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,367 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Utilities to await arbitrary awaitable objects using the C# awaitable pattern.
/// Supports Task, Task<T>, ValueTask, ValueTask<T>, UniTask, UniTask<T> and any type
/// implementing GetAwaiter/OnCompleted/GetResult.
/// </summary>
internal static class AwaitableHelper
{
public static async Task<object> AwaitIfNeeded(object value)
{
if (value == null)
{
return null;
}
Type valueType = value.GetType();
// Task and Task<T>
if (typeof(Task).IsAssignableFrom(valueType))
{
Task task = (Task)value;
await task.ConfigureAwait(false);
if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Task<>))
{
PropertyInfo resultProperty = valueType.GetProperty("Result");
if (resultProperty != null)
{
object taskResult = resultProperty.GetValue(value);
return taskResult;
}
}
return null;
}
// ValueTask and ValueTask<T> via AsTask if available
if (IsValueTask(valueType))
{
Task asTask = ConvertValueTaskToTask(value);
await asTask.ConfigureAwait(false);
return null;
}
if (IsGenericValueTask(valueType))
{
Task asTask = ConvertGenericValueTaskToTask(value);
await asTask.ConfigureAwait(false);
Type[] genericArgs = valueType.GetGenericArguments();
if (genericArgs != null && genericArgs.Length == 1)
{
Type taskType = typeof(Task<>).MakeGenericType(genericArgs[0]);
PropertyInfo resultProperty = taskType.GetProperty("Result");
if (resultProperty != null)
{
object resultValue = resultProperty.GetValue(asTask);
return resultValue;
}
}
return null;
}
// Awaitable pattern fallback (e.g., UniTask/UniTask<T> or custom awaitables)
MethodInfo getAwaiterMethod = valueType.GetMethod("GetAwaiter", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (getAwaiterMethod != null)
{
object awaiter = getAwaiterMethod.Invoke(value, null);
if (awaiter == null)
{
return value;
}
Type awaiterType = awaiter.GetType();
PropertyInfo isCompletedProperty = awaiterType.GetProperty("IsCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo getResultMethod = awaiterType.GetMethod("GetResult", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
MethodInfo onCompletedMethod = awaiterType.GetMethod("OnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
MethodInfo unsafeOnCompletedMethod = awaiterType.GetMethod("UnsafeOnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
if (isCompletedProperty != null)
{
bool isCompleted = (bool)isCompletedProperty.GetValue(awaiter);
if (isCompleted)
{
object completedResult = InvokeGetResultSafely(getResultMethod, awaiter);
return completedResult;
}
}
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
Action continuation = () =>
{
try
{
object continuationResult = InvokeGetResultSafely(getResultMethod, awaiter);
tcs.TrySetResult(continuationResult);
}
catch (TargetInvocationException tie)
{
Exception inner = tie.InnerException ?? tie;
tcs.TrySetException(inner);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
};
if (unsafeOnCompletedMethod != null)
{
unsafeOnCompletedMethod.Invoke(awaiter, new object[] { continuation });
}
else if (onCompletedMethod != null)
{
onCompletedMethod.Invoke(awaiter, new object[] { continuation });
}
else
{
// No completion registration; treat as completed
object immediateResult = InvokeGetResultSafely(getResultMethod, awaiter);
return immediateResult;
}
object awaited = await tcs.Task.ConfigureAwait(false);
return awaited;
}
// Not awaitable; return as-is
return value;
}
private static bool IsValueTask(Type type)
{
return type.FullName == "System.Threading.Tasks.ValueTask";
}
private static bool IsGenericValueTask(Type type)
{
if (!type.IsGenericType)
{
return false;
}
Type generic = type.GetGenericTypeDefinition();
return generic.FullName == "System.Threading.Tasks.ValueTask`1";
}
private static Task ConvertValueTaskToTask(object valueTask)
{
Type type = valueTask.GetType();
MethodInfo asTaskMethod = type.GetMethod("AsTask", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
if (asTaskMethod != null)
{
object taskObj = asTaskMethod.Invoke(valueTask, null);
return (Task)taskObj;
}
// Fallback: build a Task from awaiter
MethodInfo getAwaiter = type.GetMethod("GetAwaiter", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (getAwaiter == null)
{
return Task.CompletedTask;
}
object awaiter = getAwaiter.Invoke(valueTask, null);
Type awaiterType = awaiter.GetType();
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
MethodInfo onCompleted = awaiterType.GetMethod("OnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
MethodInfo unsafeOnCompleted = awaiterType.GetMethod("UnsafeOnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
MethodInfo getResult = awaiterType.GetMethod("GetResult", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
PropertyInfo isCompletedProperty = awaiterType.GetProperty("IsCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
bool hasIsCompleted = false;
bool isCompleted = false;
if (isCompletedProperty != null)
{
try
{
object isCompletedValue = isCompletedProperty.GetValue(awaiter);
if (isCompletedValue is bool boolValue)
{
isCompleted = boolValue;
hasIsCompleted = true;
}
}
catch (TargetInvocationException tie)
{
Exception inner = tie.InnerException ?? tie;
tcs.TrySetException(inner);
return tcs.Task;
}
catch (Exception ex)
{
tcs.TrySetException(ex);
return tcs.Task;
}
}
Action cont = () =>
{
try
{
if (getResult != null)
{
getResult.Invoke(awaiter, null);
}
tcs.TrySetResult(null);
}
catch (TargetInvocationException tie)
{
Exception inner = tie.InnerException ?? tie;
tcs.TrySetException(inner);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
};
if (hasIsCompleted && isCompleted)
{
cont();
return tcs.Task;
}
if (unsafeOnCompleted != null)
{
unsafeOnCompleted.Invoke(awaiter, new object[] { cont });
}
else if (onCompleted != null)
{
onCompleted.Invoke(awaiter, new object[] { cont });
}
else
{
if (hasIsCompleted && isCompleted)
{
cont();
}
else
{
_ = Task.Run(cont);
}
}
return tcs.Task;
}
private static Task ConvertGenericValueTaskToTask(object valueTaskT)
{
Type type = valueTaskT.GetType();
MethodInfo asTaskMethod = type.GetMethod("AsTask", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
if (asTaskMethod != null)
{
object taskObj = asTaskMethod.Invoke(valueTaskT, null);
return (Task)taskObj;
}
// Fallback through awaiter
MethodInfo getAwaiter = type.GetMethod("GetAwaiter", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (getAwaiter == null)
{
return Task.CompletedTask;
}
object awaiter = getAwaiter.Invoke(valueTaskT, null);
Type awaiterType = awaiter.GetType();
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
MethodInfo onCompleted = awaiterType.GetMethod("OnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
MethodInfo unsafeOnCompleted = awaiterType.GetMethod("UnsafeOnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
MethodInfo getResult = awaiterType.GetMethod("GetResult", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
PropertyInfo isCompletedProperty = awaiterType.GetProperty("IsCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
bool hasIsCompleted = false;
bool isCompleted = false;
if (isCompletedProperty != null)
{
try
{
object isCompletedValue = isCompletedProperty.GetValue(awaiter);
if (isCompletedValue is bool boolValue)
{
isCompleted = boolValue;
hasIsCompleted = true;
}
}
catch (TargetInvocationException tie)
{
Exception inner = tie.InnerException ?? tie;
tcs.TrySetException(inner);
return tcs.Task;
}
catch (Exception ex)
{
tcs.TrySetException(ex);
return tcs.Task;
}
}
Action cont = () =>
{
try
{
if (getResult != null)
{
object _ = getResult.Invoke(awaiter, null);
}
tcs.TrySetResult(null);
}
catch (TargetInvocationException tie)
{
Exception inner = tie.InnerException ?? tie;
tcs.TrySetException(inner);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
};
if (hasIsCompleted && isCompleted)
{
cont();
return tcs.Task;
}
if (unsafeOnCompleted != null)
{
unsafeOnCompleted.Invoke(awaiter, new object[] { cont });
}
else if (onCompleted != null)
{
onCompleted.Invoke(awaiter, new object[] { cont });
}
else
{
if (hasIsCompleted && isCompleted)
{
cont();
}
else
{
_ = Task.Run(cont);
}
}
return tcs.Task;
}
private static object InvokeGetResultSafely(MethodInfo getResultMethod, object awaiter)
{
if (getResultMethod == null)
{
return null;
}
object result = getResultMethod.Invoke(awaiter, null);
return result;
}
}
}

View File

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

View File

@@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Runs the prepared wrapper entry point while keeping Undo and cancellation handling consistent.
/// </summary>
public class CommandRunner : ICompiledCommandInvoker
{
private readonly CompiledCommandEntryPointResolver _entryPointResolver;
private bool _isRunning = false;
private CancellationTokenSource _cancellationTokenSource;
public bool IsRunning => _isRunning;
public CommandRunner()
: this(DynamicCodeServices.CommandEntryPointResolver)
{
}
internal CommandRunner(CompiledCommandEntryPointResolver entryPointResolver)
{
_entryPointResolver = entryPointResolver;
}
private static void LogExecutionError(Exception ex, string correlationId)
{
VibeLogger.LogError(
"command_execution_error",
"Command execution failed with exception",
new {
error = ex.Message,
stackTrace = ex.StackTrace
},
correlationId,
"Unexpected execution error occurred",
"Investigate execution failures and improve error handling"
);
}
private CancellationTokenSource CreateCombinedCancellationTokenSource(ExecutionContext context)
{
return CancellationTokenSource.CreateLinkedTokenSource(
_cancellationTokenSource.Token,
context.CancellationToken
);
}
public ExecutionResult Execute(ExecutionContext context)
{
string correlationId = McpConstants.GenerateCorrelationId();
if (!TryBeginExecution(out int undoGroup))
{
return CreateErrorResult(McpConstants.ERROR_MESSAGE_EXECUTION_IN_PROGRESS);
}
try
{
using CancellationTokenSource combinedCts = CreateCombinedCancellationTokenSource(context);
return ExecuteInternal(context, combinedCts.Token);
}
catch (OperationCanceledException)
{
return CreateCancelledResult();
}
catch (Exception ex)
{
LogExecutionError(ex, correlationId);
return CreateErrorResult(
ex.Message,
new List<string> { $"Exception: {ex.Message}" });
}
finally
{
EndExecution(undoGroup);
}
}
public void Cancel()
{
_cancellationTokenSource?.Cancel();
}
public async Task<ExecutionResult> ExecuteAsync(ExecutionContext context)
{
string correlationId = McpConstants.GenerateCorrelationId();
if (!TryBeginExecution(out int undoGroup))
{
return CreateErrorResult(McpConstants.ERROR_MESSAGE_EXECUTION_IN_PROGRESS);
}
try
{
using CancellationTokenSource combinedCts = CreateCombinedCancellationTokenSource(context);
return await ExecuteInternalAsync(context, combinedCts.Token);
}
catch (OperationCanceledException)
{
return CreateCancelledResult();
}
catch (Exception ex)
{
LogExecutionError(ex, correlationId);
return CreateErrorResult(
ex.Message,
new List<string> { $"Exception: {ex.Message}" });
}
finally
{
EndExecution(undoGroup);
}
}
private bool TryBeginExecution(out int undoGroup)
{
undoGroup = -1;
if (_isRunning)
{
return false;
}
_isRunning = true;
_cancellationTokenSource = new CancellationTokenSource();
undoGroup = Undo.GetCurrentGroup();
Undo.SetCurrentGroupName("ExecuteDynamicCode");
return true;
}
private void EndExecution(int undoGroup)
{
Undo.CollapseUndoOperations(undoGroup);
_isRunning = false;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
private static ExecutionResult CreateErrorResult(string errorMessage, List<string> logs = null)
{
return new ExecutionResult
{
Success = false,
ErrorMessage = errorMessage,
ExecutionTime = TimeSpan.Zero,
Logs = logs ?? new List<string>()
};
}
private static ExecutionResult CreateCancelledResult()
{
return CreateErrorResult(
McpConstants.ERROR_MESSAGE_EXECUTION_CANCELLED,
new List<string> { "Execution cancelled" });
}
private static ExecutionResult CreateSuccessResult(string result, List<string> logs = null)
{
return new ExecutionResult
{
Success = true,
Result = result,
ExecutionTime = TimeSpan.Zero, // Will be set by the caller
Logs = logs ?? new List<string> { "Execution completed successfully" }
};
}
private static object CreateInstance(Type targetType)
{
return Activator.CreateInstance(targetType);
}
private static object InvokeExecuteMethod(
MethodInfo executeMethod,
object instance,
Dictionary<string, object> parameters,
CancellationToken cancellationToken)
{
object[] callArgs = BuildSupportedArguments(executeMethod, parameters, cancellationToken);
return executeMethod.Invoke(instance, callArgs);
}
private ExecutionResult ExecuteInternal(ExecutionContext context, CancellationToken cancellationToken)
{
if (context.CompiledAssembly == null)
{
return CreateErrorResult(McpConstants.ERROR_MESSAGE_NO_COMPILED_ASSEMBLY);
}
try
{
// Find executable type from assembly (sync only)
(Type targetType, MethodInfo executeMethod) = _entryPointResolver.TryFindExecuteMethod(
context.CompiledAssembly);
if (targetType == null || executeMethod == null)
{
return CreateErrorResult(
McpConstants.ERROR_MESSAGE_NO_EXECUTE_METHOD,
new List<string> { "Assembly types checked but no Execute method found" });
}
// Create instance
object instance = CreateInstance(targetType);
if (instance == null)
{
return CreateErrorResult(McpConstants.ERROR_MESSAGE_FAILED_TO_CREATE_INSTANCE);
}
// Check cancellation
cancellationToken.ThrowIfCancellationRequested();
try
{
// Execute method
object executionResult = InvokeExecuteMethod(
executeMethod,
instance,
context.Parameters,
cancellationToken);
// Convert result to string
string resultString = executionResult?.ToString() ?? "";
return CreateSuccessResult(resultString);
}
catch (NotSupportedException ex)
{
return CreateErrorResult(
McpConstants.ERROR_MESSAGE_UNSUPPORTED_SIGNATURE,
new List<string> { ex.Message });
}
}
catch (OperationCanceledException)
{
throw;
}
catch (TargetInvocationException ex)
{
// Retrieve actual exception
Exception innerException = ex.InnerException ?? ex;
if (innerException is OperationCanceledException)
{
throw innerException;
}
return CreateErrorResult(
innerException.Message,
new List<string>
{
$"Target invocation exception: {innerException.Message}",
$"Stack trace: {innerException.StackTrace}"
});
}
catch (Exception ex)
{
return CreateErrorResult(
ex.Message,
new List<string>
{
$"Execution exception: {ex.Message}",
$"Stack trace: {ex.StackTrace}"
});
}
}
private async Task<ExecutionResult> ExecuteInternalAsync(ExecutionContext context, CancellationToken cancellationToken)
{
if (context.CompiledAssembly == null)
{
return CreateErrorResult(McpConstants.ERROR_MESSAGE_NO_COMPILED_ASSEMBLY);
}
try
{
// Prefer async ExecuteAsync; fallback to sync Execute
(Type asyncType, MethodInfo executeAsyncMethod) = _entryPointResolver.TryFindExecuteAsyncMethod(
context.CompiledAssembly);
if (asyncType != null && executeAsyncMethod != null)
{
object instance = CreateInstance(asyncType);
if (instance == null)
{
return CreateErrorResult(McpConstants.ERROR_MESSAGE_FAILED_TO_CREATE_INSTANCE);
}
cancellationToken.ThrowIfCancellationRequested();
object[] callArgs = BuildSupportedArguments(
executeAsyncMethod,
context.Parameters,
cancellationToken);
object invoked = executeAsyncMethod.Invoke(instance, callArgs);
object awaitedResult = await io.github.hatayama.uLoopMCP.AwaitableHelper.AwaitIfNeeded(invoked);
string resultString = awaitedResult?.ToString() ?? "";
return CreateSuccessResult(resultString);
}
// Fallback to sync path if no async method found
ExecutionResult syncResult = ExecuteInternal(context, cancellationToken);
return syncResult;
}
catch (OperationCanceledException)
{
throw;
}
catch (TargetInvocationException ex)
{
Exception innerException = ex.InnerException ?? ex;
if (innerException is OperationCanceledException)
{
throw innerException;
}
return CreateErrorResult(
innerException.Message,
new List<string>
{
$"Target invocation exception: {innerException.Message}",
$"Stack trace: {innerException.StackTrace}"
});
}
catch (Exception ex)
{
return CreateErrorResult(
ex.Message,
new List<string>
{
$"Execution exception: {ex.Message}",
$"Stack trace: {ex.StackTrace}"
});
}
}
private static object[] BuildSupportedArguments(
MethodInfo method,
Dictionary<string, object> parameters,
CancellationToken cancellationToken)
{
System.Reflection.ParameterInfo[] methodParameters = method.GetParameters();
if (methodParameters.Length == 0)
{
return null;
}
if (methodParameters.Length == 2 && methodParameters[0].ParameterType == typeof(Dictionary<string, object>) && methodParameters[1].ParameterType == typeof(CancellationToken))
{
return new object[] { parameters, cancellationToken };
}
if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(Dictionary<string, object>))
{
return new object[] { parameters };
}
if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(CancellationToken))
{
return new object[] { cancellationToken };
}
throw new NotSupportedException(
"Expected Execute/ExecuteAsync overloads with Dictionary<string,object> and/or CancellationToken");
}
}
}

View File

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

View File

@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Assembly = System.Reflection.Assembly;
namespace io.github.hatayama.uLoopMCP
{
internal sealed class CompiledCommandEntryPointResolver
{
private const string WrappedExecuteMethodName = "Execute";
private const string WrappedExecuteAsyncMethodName = "ExecuteAsync";
public (Type targetType, MethodInfo executeMethod) TryFindExecuteMethod(Assembly assembly)
{
Type wrappedType = assembly.GetType(
$"{DynamicCodeConstants.DEFAULT_NAMESPACE}.{DynamicCodeConstants.DEFAULT_CLASS_NAME}",
false);
if (wrappedType != null)
{
MethodInfo directMethod = FindPreferredExecuteMethod(wrappedType);
if (directMethod != null)
{
return (wrappedType, directMethod);
}
}
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
MethodInfo method = FindPreferredExecuteMethod(type);
if (method != null)
{
return (type, method);
}
}
return (null, null);
}
private static MethodInfo FindPreferredExecuteMethod(Type type)
{
MethodInfo methodWithParametersAndCancellation = type.GetMethod(
WrappedExecuteMethodName,
BindingFlags.Public | BindingFlags.Instance,
null,
new Type[] { typeof(Dictionary<string, object>), typeof(CancellationToken) },
null);
if (methodWithParametersAndCancellation != null)
{
return methodWithParametersAndCancellation;
}
MethodInfo methodWithParameters = type.GetMethod(
WrappedExecuteMethodName,
BindingFlags.Public | BindingFlags.Instance,
null,
new Type[] { typeof(Dictionary<string, object>) },
null);
if (methodWithParameters != null)
{
return methodWithParameters;
}
MethodInfo methodWithCancellation = type.GetMethod(
WrappedExecuteMethodName,
BindingFlags.Public | BindingFlags.Instance,
null,
new Type[] { typeof(CancellationToken) },
null);
if (methodWithCancellation != null)
{
return methodWithCancellation;
}
return type.GetMethod(
WrappedExecuteMethodName,
BindingFlags.Public | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
}
public (Type targetType, MethodInfo executeAsyncMethod) TryFindExecuteAsyncMethod(Assembly assembly)
{
Type wrappedType = assembly.GetType(
$"{DynamicCodeConstants.DEFAULT_NAMESPACE}.{DynamicCodeConstants.DEFAULT_CLASS_NAME}",
false);
if (wrappedType != null)
{
MethodInfo directMethod = FindPreferredExecuteAsyncMethod(wrappedType);
if (directMethod != null)
{
return (wrappedType, directMethod);
}
}
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
MethodInfo method = FindPreferredExecuteAsyncMethod(type);
if (method != null)
{
return (type, method);
}
}
return (null, null);
}
private static MethodInfo FindPreferredExecuteAsyncMethod(Type type)
{
MethodInfo methodWithParametersAndCancellation = type.GetMethod(
WrappedExecuteAsyncMethodName,
BindingFlags.Public | BindingFlags.Instance,
null,
new Type[] { typeof(Dictionary<string, object>), typeof(CancellationToken) },
null);
if (methodWithParametersAndCancellation != null)
{
return methodWithParametersAndCancellation;
}
MethodInfo methodWithParameters = type.GetMethod(
WrappedExecuteAsyncMethodName,
BindingFlags.Public | BindingFlags.Instance,
null,
new Type[] { typeof(Dictionary<string, object>) },
null);
if (methodWithParameters != null)
{
return methodWithParameters;
}
MethodInfo methodWithCancellation = type.GetMethod(
WrappedExecuteAsyncMethodName,
BindingFlags.Public | BindingFlags.Instance,
null,
new Type[] { typeof(CancellationToken) },
null);
if (methodWithCancellation != null)
{
return methodWithCancellation;
}
return type.GetMethod(
WrappedExecuteAsyncMethodName,
BindingFlags.Public | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
}
}
}

View File

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

View File

@@ -0,0 +1,106 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using Stopwatch = System.Diagnostics.Stopwatch;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Provides the runtime-facing facade for execute-dynamic-code.
/// Entry points depend on this facade instead of reaching into factory and executor wiring directly.
/// </summary>
internal sealed class DynamicCodeExecutionFacade : IShutdownAwareDynamicCodeExecutionRuntime, IDisposable
{
private readonly ICompiledAssemblyBuilder _assemblyBuilder;
private readonly IDynamicCodeExecutorPool _executorPool;
private readonly DynamicCodeExecutionScheduler _executionScheduler;
public DynamicCodeExecutionFacade(
ICompiledAssemblyBuilder assemblyBuilder,
IDynamicCodeExecutorPool executorPool)
{
_assemblyBuilder = assemblyBuilder ?? throw new ArgumentNullException(nameof(assemblyBuilder));
_executorPool = executorPool ?? throw new ArgumentNullException(nameof(executorPool));
_executionScheduler = new DynamicCodeExecutionScheduler(_executorPool.Dispose);
}
public bool SupportsAutoPrewarm()
{
_executionScheduler.ThrowIfDisposed();
return _assemblyBuilder.SupportsAutoPrewarm();
}
public async Task<ExecutionResult> ExecuteAsync(
DynamicCodeExecutionRequest request,
CancellationToken cancellationToken = default)
{
Debug.Assert(request != null, "request must not be null");
Debug.Assert(!string.IsNullOrWhiteSpace(request.Code), "request.Code must not be empty");
return await _executionScheduler.RunForegroundAsync(
innerCancellationToken => ExecuteCoreAsync(request, innerCancellationToken),
CreateExecutionInProgressResult,
cancellationToken);
}
public async Task<(bool Entered, ExecutionResult Result)> TryExecuteIfIdleAsync(
DynamicCodeExecutionRequest request,
CancellationToken cancellationToken = default)
{
Debug.Assert(request != null, "request must not be null");
Debug.Assert(!string.IsNullOrWhiteSpace(request.Code), "request.Code must not be empty");
return await _executionScheduler.TryRunIfIdleAsync(
request.YieldToForegroundRequests,
innerCancellationToken => ExecuteCoreAsync(request, innerCancellationToken),
cancellationToken);
}
private async Task<ExecutionResult> ExecuteCoreAsync(
DynamicCodeExecutionRequest request,
CancellationToken cancellationToken)
{
Stopwatch executorAcquireStopwatch = Stopwatch.StartNew();
IDynamicCodeExecutor executor = _executorPool.GetOrCreate(request.SecurityLevel);
executorAcquireStopwatch.Stop();
Stopwatch executorTotalStopwatch = Stopwatch.StartNew();
ExecutionResult result = await executor.ExecuteCodeAsync(
request.Code,
request.ClassName,
request.Parameters,
cancellationToken,
request.CompileOnly);
executorTotalStopwatch.Stop();
if (result.Timings == null)
{
result.Timings = new System.Collections.Generic.List<string>();
}
result.Timings.Add($"[Perf] ExecutorAcquire: {executorAcquireStopwatch.Elapsed.TotalMilliseconds:F1}ms");
result.Timings.Add($"[Perf] ExecutorTotal: {executorTotalStopwatch.Elapsed.TotalMilliseconds:F1}ms");
return result;
}
private static ExecutionResult CreateExecutionInProgressResult()
{
return new ExecutionResult
{
Success = false,
ErrorMessage = McpConstants.ERROR_MESSAGE_EXECUTION_IN_PROGRESS
};
}
public void Dispose()
{
_executionScheduler.Dispose();
}
public Task ShutdownAsync()
{
return _executionScheduler.ShutdownAsync();
}
}
}

View File

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

View File

@@ -0,0 +1,17 @@
namespace io.github.hatayama.uLoopMCP
{
internal sealed class DynamicCodeExecutionRequest
{
public string Code { get; set; }
public string ClassName { get; set; } = DynamicCodeConstants.DEFAULT_CLASS_NAME;
public object[] Parameters { get; set; }
public bool CompileOnly { get; set; }
public DynamicCodeSecurityLevel SecurityLevel { get; set; }
public bool YieldToForegroundRequests { get; set; }
}
}

View File

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

View File

@@ -0,0 +1,314 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
internal sealed class DynamicCodeExecutionScheduler : IDisposable
{
private const int BusyHandoffWindowMilliseconds = 50;
private const int CancelledPrewarmHandoffWindowMilliseconds = 500;
private readonly Action _disposeResources;
private readonly DynamicCodeExecutionSchedulerHooks _hooks;
private readonly int _busyHandoffWindowMilliseconds;
private readonly int _cancelledPrewarmHandoffWindowMilliseconds;
private readonly SemaphoreSlim _executionSemaphore = new(1, 1);
private readonly CancellationTokenSource _lifetimeCancellationTokenSource = new();
private readonly TaskCompletionSource<bool> _shutdownCompletionSource =
new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly object _disposeLock = new();
private readonly object _executionStateLock = new();
private readonly object _backgroundPrewarmTransitionLock = new();
private CancellationTokenSource _backgroundPrewarmCancellationTokenSource;
private bool _backgroundPrewarmInProgress;
private bool _resourcesDisposed;
private bool _disposed;
public DynamicCodeExecutionScheduler(
Action disposeResources,
DynamicCodeExecutionSchedulerHooks hooks = null,
int busyHandoffWindowMilliseconds = BusyHandoffWindowMilliseconds,
int cancelledPrewarmHandoffWindowMilliseconds = CancelledPrewarmHandoffWindowMilliseconds)
{
_disposeResources = disposeResources ?? throw new ArgumentNullException(nameof(disposeResources));
_hooks = hooks ?? new DynamicCodeExecutionSchedulerHooks();
_busyHandoffWindowMilliseconds = busyHandoffWindowMilliseconds;
_cancelledPrewarmHandoffWindowMilliseconds = cancelledPrewarmHandoffWindowMilliseconds;
}
public void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DynamicCodeExecutionScheduler));
}
}
public async Task<T> RunForegroundAsync<T>(
Func<CancellationToken, Task<T>> action,
Func<T> createBusyResult,
CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();
bool entered = await _executionSemaphore.WaitAsync(0, cancellationToken);
if (!entered)
{
await _hooks.InvokeAfterBusySemaphoreProbeFailedAsync();
if (!TryCancelBackgroundPrewarm())
{
entered = await TryAcquireAfterBusyHandoffAsync(cancellationToken);
if (!entered)
{
return createBusyResult();
}
}
else
{
entered = await TryAcquireAfterBusyHandoffAsync(
cancellationToken,
_cancelledPrewarmHandoffWindowMilliseconds);
if (!entered)
{
return createBusyResult();
}
}
}
CancellationTokenSource executionCancellationTokenSource = null;
try
{
_hooks.AfterSemaphoreEntered?.Invoke();
ThrowIfDisposed();
executionCancellationTokenSource =
CreateExecutionCancellationTokenSource(cancellationToken);
SetExecutionState(false, executionCancellationTokenSource);
return await action(executionCancellationTokenSource.Token);
}
finally
{
ClearExecutionState(false, executionCancellationTokenSource);
executionCancellationTokenSource?.Dispose();
DisposeResourcesIfRequested();
_executionSemaphore.Release();
}
}
public async Task<(bool Entered, T Result)> TryRunIfIdleAsync<T>(
bool yieldToForegroundRequests,
Func<CancellationToken, Task<T>> action,
CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();
if (!yieldToForegroundRequests)
{
return await TryRunWithoutForegroundYieldAsync(action, cancellationToken);
}
bool entered;
CancellationTokenSource executionCancellationTokenSource = null;
lock (_backgroundPrewarmTransitionLock)
{
entered = _executionSemaphore.Wait(0);
if (!entered)
{
return (false, default);
}
ThrowIfDisposed();
executionCancellationTokenSource =
CreateExecutionCancellationTokenSource(cancellationToken);
SetExecutionState(true, executionCancellationTokenSource);
}
try
{
await _hooks.InvokeAfterBackgroundExecutionStatePublishedAsync();
_hooks.AfterSemaphoreEntered?.Invoke();
T result = await action(executionCancellationTokenSource.Token);
return (true, result);
}
finally
{
ClearExecutionState(true, executionCancellationTokenSource);
executionCancellationTokenSource?.Dispose();
DisposeResourcesIfRequested();
_executionSemaphore.Release();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_lifetimeCancellationTokenSource.Cancel();
if (!_executionSemaphore.Wait(0))
{
return;
}
try
{
DisposeResourcesIfRequested();
}
finally
{
_executionSemaphore.Release();
}
}
public Task ShutdownAsync()
{
Dispose();
return _shutdownCompletionSource.Task;
}
private async Task<(bool Entered, T Result)> TryRunWithoutForegroundYieldAsync<T>(
Func<CancellationToken, Task<T>> action,
CancellationToken cancellationToken)
{
bool entered = await _executionSemaphore.WaitAsync(0, cancellationToken);
if (!entered)
{
return (false, default);
}
CancellationTokenSource executionCancellationTokenSource = null;
try
{
_hooks.AfterSemaphoreEntered?.Invoke();
ThrowIfDisposed();
executionCancellationTokenSource =
CreateExecutionCancellationTokenSource(cancellationToken);
SetExecutionState(false, executionCancellationTokenSource);
T result = await action(executionCancellationTokenSource.Token);
return (true, result);
}
finally
{
ClearExecutionState(false, executionCancellationTokenSource);
executionCancellationTokenSource?.Dispose();
DisposeResourcesIfRequested();
_executionSemaphore.Release();
}
}
private bool TryCancelBackgroundPrewarm()
{
lock (_executionStateLock)
{
if (!_backgroundPrewarmInProgress || _backgroundPrewarmCancellationTokenSource == null)
{
return false;
}
_backgroundPrewarmCancellationTokenSource.Cancel();
return true;
}
}
private async Task<bool> TryAcquireAfterBusyHandoffAsync(
CancellationToken cancellationToken,
int handoffWindowMilliseconds = -1)
{
if (handoffWindowMilliseconds < 0)
{
handoffWindowMilliseconds = _busyHandoffWindowMilliseconds;
}
using CancellationTokenSource handoffCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
_lifetimeCancellationTokenSource.Token);
handoffCancellationTokenSource.CancelAfter(handoffWindowMilliseconds);
try
{
await _executionSemaphore.WaitAsync(handoffCancellationTokenSource.Token);
return true;
}
catch (OperationCanceledException)
{
if (cancellationToken.IsCancellationRequested ||
_lifetimeCancellationTokenSource.IsCancellationRequested)
{
throw;
}
return false;
}
}
private CancellationTokenSource CreateExecutionCancellationTokenSource(
CancellationToken cancellationToken)
{
return CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
_lifetimeCancellationTokenSource.Token);
}
private void SetExecutionState(
bool yieldToForegroundRequests,
CancellationTokenSource executionCancellationTokenSource)
{
lock (_executionStateLock)
{
_backgroundPrewarmInProgress = yieldToForegroundRequests;
_backgroundPrewarmCancellationTokenSource = yieldToForegroundRequests
? executionCancellationTokenSource
: null;
}
}
private void ClearExecutionState(
bool yieldToForegroundRequests,
CancellationTokenSource executionCancellationTokenSource)
{
if (!yieldToForegroundRequests)
{
return;
}
lock (_executionStateLock)
{
if (!ReferenceEquals(_backgroundPrewarmCancellationTokenSource, executionCancellationTokenSource))
{
return;
}
_backgroundPrewarmInProgress = false;
_backgroundPrewarmCancellationTokenSource = null;
}
}
private void DisposeResourcesIfRequested()
{
if (!_disposed)
{
return;
}
lock (_disposeLock)
{
if (_resourcesDisposed)
{
return;
}
_resourcesDisposed = true;
}
_disposeResources();
_lifetimeCancellationTokenSource.Dispose();
_shutdownCompletionSource.TrySetResult(true);
}
}
}

View File

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

View File

@@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
internal sealed class DynamicCodeExecutionSchedulerHooks
{
public Action AfterSemaphoreEntered { get; set; }
public Func<Task> AfterBackgroundExecutionStatePublishedAsync { get; set; }
public Func<Task> AfterBusySemaphoreProbeFailedAsync { get; set; }
public async Task InvokeAfterBackgroundExecutionStatePublishedAsync()
{
if (AfterBackgroundExecutionStatePublishedAsync == null)
{
return;
}
await AfterBackgroundExecutionStatePublishedAsync();
}
public async Task InvokeAfterBusySemaphoreProbeFailedAsync()
{
if (AfterBusySemaphoreProbeFailedAsync == null)
{
return;
}
await AfterBusySemaphoreProbeFailedAsync();
}
}
}

View File

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

View File

@@ -0,0 +1,440 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Bridges compilation and execution so the tool layer can stay focused on request and response shaping.
/// </summary>
internal sealed class DynamicCodeExecutor : IDynamicCodeExecutor
{
private readonly IDynamicCompilationService _compiler;
private readonly ICompiledCommandInvoker _invoker;
private readonly DynamicCodeSourcePreparationService _sourcePreparationService;
private readonly ExecutionStatistics _statistics;
private readonly object _statsLock = new();
public DynamicCodeExecutor(
IDynamicCompilationService compiler,
ICompiledCommandInvoker invoker)
: this(compiler, invoker, DynamicCodeServices.SourcePreparationService)
{
}
internal DynamicCodeExecutor(
IDynamicCompilationService compiler,
ICompiledCommandInvoker invoker,
DynamicCodeSourcePreparationService sourcePreparationService)
{
_compiler = compiler ?? throw new ArgumentNullException(nameof(compiler));
_invoker = invoker ?? throw new ArgumentNullException(nameof(invoker));
_sourcePreparationService = sourcePreparationService ?? throw new ArgumentNullException(nameof(sourcePreparationService));
_statistics = new ExecutionStatistics();
}
public async Task<ExecutionResult> ExecuteCodeAsync(
string code,
string className = DynamicCodeConstants.DEFAULT_CLASS_NAME,
object[] parameters = null,
CancellationToken cancellationToken = default,
bool compileOnly = false)
{
string correlationId = McpConstants.GenerateCorrelationId();
Stopwatch totalStopwatch = Stopwatch.StartNew();
Stopwatch sourcePreparationStopwatch = Stopwatch.StartNew();
try
{
PreparedDynamicCode preparedCode = _sourcePreparationService.Prepare(
code,
DynamicCodeConstants.DEFAULT_NAMESPACE,
className);
sourcePreparationStopwatch.Stop();
Stopwatch compileTotalStopwatch = Stopwatch.StartNew();
CompilationResult compilationResult = await CompileCodeAsync(code, className, cancellationToken);
compileTotalStopwatch.Stop();
ExecutionResult compilationFailureResult = TryCreateCompilationFailureResult(
compilationResult,
totalStopwatch);
if (compilationFailureResult != null)
{
AppendExecutorStageTimings(
compilationFailureResult.Timings,
sourcePreparationStopwatch.Elapsed.TotalMilliseconds,
compileTotalStopwatch.Elapsed.TotalMilliseconds);
return compilationFailureResult;
}
if (compileOnly)
{
ExecutionResult compileOnlyResult = CreateCompileOnlySuccessResult(compilationResult, totalStopwatch.Elapsed);
AppendExecutorStageTimings(
compileOnlyResult.Timings,
sourcePreparationStopwatch.Elapsed.TotalMilliseconds,
compileTotalStopwatch.Elapsed.TotalMilliseconds);
return compileOnlyResult;
}
ExecutionContext context = new ExecutionContext
{
CompiledAssembly = compilationResult.CompiledAssembly,
Parameters = BuildExecutionParameters(parameters, preparedCode.HoistedLiteralBindings),
CancellationToken = cancellationToken
};
Stopwatch executionStopwatch = Stopwatch.StartNew();
ExecutionResult executionResult = await _invoker.ExecuteAsync(context);
executionStopwatch.Stop();
executionResult.ExecutionTime = totalStopwatch.Elapsed;
executionResult.AutoInjectedNamespaces = compilationResult.AutoInjectedNamespaces;
executionResult.Timings = MergeTimings(
compilationResult.Timings,
executionResult.Timings,
$"[Perf] Execution: {executionStopwatch.Elapsed.TotalMilliseconds:F1}ms");
AppendExecutorStageTimings(
executionResult.Timings,
sourcePreparationStopwatch.Elapsed.TotalMilliseconds,
compileTotalStopwatch.Elapsed.TotalMilliseconds);
AppendCompilationAdvisories(executionResult.Logs, compilationResult.AdvisoryLogs);
UpdateStatistics(executionResult, totalStopwatch.Elapsed);
return executionResult;
}
catch (OperationCanceledException)
{
ExecutionResult cancelledResult = CreateCancelledResult(totalStopwatch.Elapsed);
UpdateStatistics(cancelledResult, totalStopwatch.Elapsed);
return cancelledResult;
}
catch (Exception ex)
{
ExecutionResult failureResult = CreateUnexpectedErrorResult(ex, totalStopwatch.Elapsed);
UpdateStatistics(failureResult, totalStopwatch.Elapsed);
LogUnexpectedExecutionException(ex, correlationId, totalStopwatch.ElapsedMilliseconds);
return failureResult;
}
}
public ExecutionStatistics GetStatistics()
{
lock (_statsLock)
{
return new ExecutionStatistics
{
TotalExecutions = _statistics.TotalExecutions,
SuccessfulExecutions = _statistics.SuccessfulExecutions,
FailedExecutions = _statistics.FailedExecutions,
AverageExecutionTime = _statistics.AverageExecutionTime,
SecurityViolations = _statistics.SecurityViolations,
CompilationErrors = _statistics.CompilationErrors
};
}
}
public void Dispose()
{
if (_compiler is IDisposable disposableCompiler)
{
disposableCompiler.Dispose();
}
}
private async Task<CompilationResult> CompileCodeAsync(
string code,
string className,
CancellationToken ct)
{
CompilationRequest request = new CompilationRequest
{
Code = code,
ClassName = className,
Namespace = DynamicCodeConstants.DEFAULT_NAMESPACE
};
CompilationResult result = await _compiler.CompileAsync(request, ct);
if (!result.Success)
{
lock (_statsLock)
{
_statistics.CompilationErrors++;
}
}
return result;
}
private ExecutionResult TryCreateCompilationFailureResult(
CompilationResult compilationResult,
Stopwatch totalStopwatch)
{
if (!compilationResult.Success)
{
if (compilationResult.HasSecurityViolations)
{
return CreateSecurityFailureResult(
"Security violations detected",
totalStopwatch.Elapsed,
compilationResult.SecurityViolations,
compilationResult.Timings);
}
return CreateCompilationFailureResult(
"Compilation error occurred",
totalStopwatch.Elapsed,
compilationResult);
}
if (compilationResult.HasSecurityViolations)
{
return CreateSecurityFailureResult(
"Security violations detected (Dangerous API call)",
totalStopwatch.Elapsed,
compilationResult.SecurityViolations,
compilationResult.Timings);
}
return null;
}
private static ExecutionResult CreateCompileOnlySuccessResult(
CompilationResult compilationResult,
TimeSpan executionTime)
{
return new ExecutionResult
{
Success = true,
Result = null,
ExecutionTime = executionTime,
Logs = MergeCompilationLogs(
new List<string> { "Code compiled successfully (no execution)" },
compilationResult.AdvisoryLogs),
AutoInjectedNamespaces = compilationResult.AutoInjectedNamespaces,
Timings = CloneTimings(compilationResult.Timings)
};
}
private static ExecutionResult CreateCancelledResult(TimeSpan executionTime)
{
return new ExecutionResult
{
Success = false,
ErrorMessage = McpConstants.ERROR_MESSAGE_EXECUTION_CANCELLED,
Logs = new List<string> { "Execution cancelled" },
ExecutionTime = executionTime,
Timings = new List<string>()
};
}
private static ExecutionResult CreateSecurityFailureResult(
string message,
TimeSpan executionTime,
List<SecurityViolation> violations,
List<string> timings)
{
List<string> violationMessages = BuildViolationMessages(violations);
if (violationMessages.Count > 0)
{
message = $"{message} {string.Join(" ", violationMessages)}";
}
return new ExecutionResult
{
Success = false,
ErrorMessage = message,
Logs = violationMessages,
ExecutionTime = executionTime,
Timings = timings != null ? new List<string>(timings) : new List<string>()
};
}
private static ExecutionResult CreateCompilationFailureResult(
string message,
TimeSpan executionTime,
CompilationResult compilationResult)
{
return new ExecutionResult
{
Success = false,
ErrorMessage = message,
Logs = MergeCompilationLogs(new List<string>(), compilationResult.AdvisoryLogs),
ExecutionTime = executionTime,
CompilationErrors = compilationResult.Errors,
UpdatedCode = compilationResult.UpdatedCode,
AmbiguousTypeCandidates = compilationResult.AmbiguousTypeCandidates,
AutoInjectedNamespaces = compilationResult.AutoInjectedNamespaces,
Timings = CloneTimings(compilationResult.Timings)
};
}
private static ExecutionResult CreateUnexpectedErrorResult(
Exception ex,
TimeSpan executionTime)
{
return new ExecutionResult
{
Success = false,
ErrorMessage = "An unexpected error occurred during execution",
Exception = ex,
Logs = new List<string> { ex.ToString() },
ExecutionTime = executionTime,
Timings = new List<string>()
};
}
private static List<string> BuildViolationMessages(List<SecurityViolation> violations)
{
List<string> violationMessages = new List<string>();
if (violations == null)
{
return violationMessages;
}
foreach (SecurityViolation violation in violations)
{
string violationMessage = !string.IsNullOrEmpty(violation.Message)
? violation.Message
: violation.Description;
if (!string.IsNullOrEmpty(violation.ApiName))
{
violationMessages.Add($"{violation.Type}: {violationMessage} (API: {violation.ApiName})");
continue;
}
violationMessages.Add($"{violation.Type}: {violationMessage}");
}
return violationMessages;
}
private static List<string> MergeCompilationLogs(
List<string> logs,
List<string> advisoryLogs)
{
List<string> mergedLogs = logs ?? new List<string>();
AppendCompilationAdvisories(mergedLogs, advisoryLogs);
return mergedLogs;
}
private static void AppendCompilationAdvisories(
List<string> destination,
List<string> advisoryLogs)
{
if (destination == null || advisoryLogs == null || advisoryLogs.Count == 0)
{
return;
}
destination.AddRange(advisoryLogs);
}
private void UpdateStatistics(ExecutionResult result, TimeSpan executionTime)
{
lock (_statsLock)
{
_statistics.TotalExecutions++;
if (result.Success)
{
_statistics.SuccessfulExecutions++;
}
else
{
_statistics.FailedExecutions++;
}
double totalMilliseconds =
_statistics.AverageExecutionTime.TotalMilliseconds * (_statistics.TotalExecutions - 1);
totalMilliseconds += executionTime.TotalMilliseconds;
_statistics.AverageExecutionTime =
TimeSpan.FromMilliseconds(totalMilliseconds / _statistics.TotalExecutions);
}
}
private static Dictionary<string, object> BuildExecutionParameters(
object[] parameters,
IReadOnlyCollection<HoistedLiteralBinding> hoistedLiteralBindings)
{
Dictionary<string, object> executionParameters = new Dictionary<string, object>();
if (hoistedLiteralBindings != null)
{
foreach (HoistedLiteralBinding binding in hoistedLiteralBindings)
{
executionParameters[binding.ParameterName] = binding.Value;
}
}
if (parameters != null)
{
for (int i = 0; i < parameters.Length; i++)
{
executionParameters[$"param{i}"] = parameters[i];
}
}
return executionParameters;
}
private static List<string> MergeTimings(
List<string> compilationTimings,
List<string> executionTimings,
string executionEntry)
{
List<string> mergedTimings = new List<string>();
if (compilationTimings != null)
{
mergedTimings.AddRange(compilationTimings);
}
if (executionTimings != null)
{
mergedTimings.AddRange(executionTimings);
}
mergedTimings.Add(executionEntry);
return mergedTimings;
}
private static void AppendExecutorStageTimings(
List<string> timings,
double sourcePreparationMilliseconds,
double compileTotalMilliseconds)
{
if (timings == null)
{
return;
}
timings.Add($"[Perf] SourcePrepare: {sourcePreparationMilliseconds:F1}ms");
timings.Add($"[Perf] CompileTotal: {compileTotalMilliseconds:F1}ms");
}
private static List<string> CloneTimings(List<string> timings)
{
return timings != null ? new List<string>(timings) : new List<string>();
}
private static void LogUnexpectedExecutionException(
Exception ex,
string correlationId,
long executionTimeMilliseconds)
{
VibeLogger.LogError(
"execute_code_exception",
"Unexpected error during code execution",
new
{
exception_type = ex.GetType().Name,
execution_time_ms = executionTimeMilliseconds
},
correlationId,
"Dynamic Code Execution Error",
"Investigating Unexpected Exception");
}
}
}

View File

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

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using io.github.hatayama.uLoopMCP.Factory;
namespace io.github.hatayama.uLoopMCP
{
internal sealed class DynamicCodeExecutorPool : IDynamicCodeExecutorPool
{
private readonly IDynamicCodeExecutorProvider _executorProvider;
private readonly Dictionary<DynamicCodeSecurityLevel, IDynamicCodeExecutor> _executorsBySecurityLevel = new();
private readonly object _executorsLock = new();
private bool _disposed;
public DynamicCodeExecutorPool(IDynamicCodeExecutorProvider executorProvider)
{
_executorProvider = executorProvider ?? throw new ArgumentNullException(nameof(executorProvider));
}
public IDynamicCodeExecutor GetOrCreate(DynamicCodeSecurityLevel securityLevel)
{
lock (_executorsLock)
{
ThrowIfDisposed();
if (_executorsBySecurityLevel.TryGetValue(securityLevel, out IDynamicCodeExecutor executor))
{
return executor;
}
IDynamicCodeExecutor createdExecutor = _executorProvider.Create(securityLevel);
if (createdExecutor is DynamicCodeExecutorStub)
{
return createdExecutor;
}
_executorsBySecurityLevel.Add(securityLevel, createdExecutor);
return createdExecutor;
}
}
public void Dispose()
{
lock (_executorsLock)
{
if (_disposed)
{
return;
}
_disposed = true;
foreach (IDynamicCodeExecutor executor in _executorsBySecurityLevel.Values)
{
executor.Dispose();
}
_executorsBySecurityLevel.Clear();
}
}
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DynamicCodeExecutorPool));
}
}
}
}

View File

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

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Fallback stub when no compilation provider is registered.
/// Returns an error for all execution attempts.
/// </summary>
public class DynamicCodeExecutorStub : IDynamicCodeExecutor
{
private readonly ExecutionStatistics _statistics;
public DynamicCodeExecutorStub()
{
_statistics = new ExecutionStatistics();
}
/// <summary>Execute code asynchronously (always returns a Roslyn required error)</summary>
public Task<ExecutionResult> ExecuteCodeAsync(
string code,
string className = DynamicCodeConstants.DEFAULT_CLASS_NAME,
object[] parameters = null,
CancellationToken cancellationToken = default,
bool compileOnly = false)
{
return Task.FromResult(CreateCompilationProviderUnavailableResult());
}
/// <summary>Retrieve execution statistics</summary>
public ExecutionStatistics GetStatistics()
{
return new ExecutionStatistics
{
TotalExecutions = _statistics.TotalExecutions,
SuccessfulExecutions = _statistics.SuccessfulExecutions,
FailedExecutions = _statistics.FailedExecutions,
AverageExecutionTime = _statistics.AverageExecutionTime,
SecurityViolations = _statistics.SecurityViolations,
CompilationErrors = _statistics.CompilationErrors
};
}
public void Dispose()
{
}
private ExecutionResult CreateCompilationProviderUnavailableResult()
{
return new ExecutionResult
{
Success = false,
ErrorMessage = "COMPILATION_PROVIDER_UNAVAILABLE: No compilation provider is registered. Check initialization.",
ExecutionTime = TimeSpan.Zero,
Statistics = _statistics
};
}
}
}

View File

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

View File

@@ -0,0 +1,96 @@
namespace io.github.hatayama.uLoopMCP
{
internal static class DynamicCodeForegroundWarmupState
{
private static readonly object SyncRoot = new();
private static ForegroundWarmupStatus _status = ForegroundWarmupStatus.Pending;
internal static bool TryBegin()
{
lock (SyncRoot)
{
if (_status != ForegroundWarmupStatus.Pending)
{
return false;
}
_status = ForegroundWarmupStatus.Running;
return true;
}
}
internal static void MarkCompleted()
{
lock (SyncRoot)
{
if (_status == ForegroundWarmupStatus.Completed)
{
return;
}
System.Diagnostics.Debug.Assert(
_status == ForegroundWarmupStatus.Running,
"Foreground warmup must only complete after it has started.");
_status = ForegroundWarmupStatus.Completed;
}
}
internal static void MarkCompletedByBackgroundWarmup()
{
lock (SyncRoot)
{
// Why: startup and recovery already run their own hidden prewarm before users see the
// first foreground request, so that successful background path should satisfy the
// one-shot fallback as well.
// Why not force the foreground fallback to run anyway: that makes warmed startups pay
// a second hidden execute-dynamic-code request and pushes the first user-visible call
// back into the hundreds of milliseconds we already worked to remove.
_status = ForegroundWarmupStatus.Completed;
}
}
internal static void MarkCompletedByForegroundExecution()
{
lock (SyncRoot)
{
// Why: a real foreground execution succeeding after a transient hidden-warmup miss
// proves the user-visible path is already usable for the next request.
// Why not insist on the hidden warmup succeeding first: that keeps Pending alive
// after the exact success case we care about and injects another needless warmup.
_status = ForegroundWarmupStatus.Completed;
}
}
internal static void ResetAfterIncompleteAttempt()
{
lock (SyncRoot)
{
// Why: a failed hidden warmup leaves the next foreground execution cold, so the next
// request still needs one more chance to pay that startup tax before users do.
// Why not mark every attempt as completed: that would permanently skip the fallback
// after a transient startup failure and bring back the slow first real execution.
if (_status == ForegroundWarmupStatus.Completed)
{
return;
}
_status = ForegroundWarmupStatus.Pending;
}
}
internal static void Reset()
{
lock (SyncRoot)
{
_status = ForegroundWarmupStatus.Pending;
}
}
private enum ForegroundWarmupStatus
{
Pending,
Running,
Completed
}
}
}

View File

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

View File

@@ -0,0 +1,147 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace io.github.hatayama.uLoopMCP
{
internal static class DynamicCodeStartupTelemetry
{
private static readonly object SyncRoot = new();
private static long _serverReadyTimestamp;
private static long _prewarmStateTimestamp;
private static long _prewarmStartedTimestamp;
private static long _prewarmFinishedTimestamp;
private static string _prewarmState = "NotRequested";
private static string _prewarmDetail = string.Empty;
public static void MarkServerReady()
{
lock (SyncRoot)
{
_serverReadyTimestamp = Stopwatch.GetTimestamp();
_prewarmStateTimestamp = 0;
_prewarmStartedTimestamp = 0;
_prewarmFinishedTimestamp = 0;
_prewarmState = "NotRequested";
_prewarmDetail = string.Empty;
}
}
public static void MarkPrewarmQueued()
{
UpdatePrewarmState("Queued", string.Empty, false);
}
public static void MarkPrewarmStarted()
{
lock (SyncRoot)
{
_prewarmStartedTimestamp = Stopwatch.GetTimestamp();
}
UpdatePrewarmState("Running", string.Empty, false);
}
public static void MarkPrewarmCompleted()
{
UpdatePrewarmState("Completed", string.Empty, true);
}
public static void MarkPrewarmSkipped(string detail)
{
UpdatePrewarmState("Skipped", detail, true);
}
public static void MarkPrewarmYielded(string detail)
{
UpdatePrewarmState("Yielded", detail, true);
}
public static void MarkPrewarmFailed(string detail)
{
UpdatePrewarmState("Failed", detail, true);
}
public static List<string> CreateTimingEntries()
{
long serverReadyTimestamp;
long prewarmStateTimestamp;
long prewarmStartedTimestamp;
long prewarmFinishedTimestamp;
string prewarmState;
string prewarmDetail;
lock (SyncRoot)
{
serverReadyTimestamp = _serverReadyTimestamp;
prewarmStateTimestamp = _prewarmStateTimestamp;
prewarmStartedTimestamp = _prewarmStartedTimestamp;
prewarmFinishedTimestamp = _prewarmFinishedTimestamp;
prewarmState = _prewarmState;
prewarmDetail = _prewarmDetail;
}
List<string> entries = new List<string>();
if (serverReadyTimestamp > 0)
{
entries.Add($"[Perf] ServerReadyAge: {ToMilliseconds(serverReadyTimestamp, Stopwatch.GetTimestamp()):F1}ms");
}
entries.Add($"[Perf] WarmReady: {string.Equals(prewarmState, "Completed")}");
entries.Add($"[Perf] PrewarmState: {prewarmState}");
if (prewarmStateTimestamp > 0)
{
entries.Add($"[Perf] PrewarmStateAge: {ToMilliseconds(prewarmStateTimestamp, Stopwatch.GetTimestamp()):F1}ms");
}
if (prewarmStartedTimestamp > 0 && prewarmFinishedTimestamp > 0)
{
entries.Add($"[Perf] PrewarmDuration: {ToMilliseconds(prewarmStartedTimestamp, prewarmFinishedTimestamp):F1}ms");
}
if (!string.IsNullOrEmpty(prewarmDetail))
{
entries.Add($"[Perf] PrewarmDetail: {prewarmDetail}");
}
return entries;
}
internal static void Reset()
{
lock (SyncRoot)
{
_serverReadyTimestamp = 0;
_prewarmStateTimestamp = 0;
_prewarmStartedTimestamp = 0;
_prewarmFinishedTimestamp = 0;
_prewarmState = "NotRequested";
_prewarmDetail = string.Empty;
}
}
private static void UpdatePrewarmState(string state, string detail, bool terminal)
{
lock (SyncRoot)
{
_prewarmState = state;
_prewarmDetail = detail ?? string.Empty;
_prewarmStateTimestamp = Stopwatch.GetTimestamp();
if (terminal)
{
_prewarmFinishedTimestamp = _prewarmStateTimestamp;
return;
}
_prewarmFinishedTimestamp = 0;
}
}
private static double ToMilliseconds(long startTimestamp, long endTimestamp)
{
long elapsedTicks = endTimestamp - startTimestamp;
return elapsedTicks * 1000d / Stopwatch.Frequency;
}
}
}

View File

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

View File

@@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
internal interface ICompiledCommandInvoker
{
Task<ExecutionResult> ExecuteAsync(ExecutionContext context);
}
}

View File

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

View File

@@ -0,0 +1,18 @@
using System.Threading;
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
internal interface IDynamicCodeExecutionRuntime
{
bool SupportsAutoPrewarm();
Task<ExecutionResult> ExecuteAsync(
DynamicCodeExecutionRequest request,
CancellationToken cancellationToken = default);
Task<(bool Entered, ExecutionResult Result)> TryExecuteIfIdleAsync(
DynamicCodeExecutionRequest request,
CancellationToken cancellationToken = default);
}
}

View File

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

View File

@@ -0,0 +1,9 @@
using System;
namespace io.github.hatayama.uLoopMCP
{
internal interface IDynamicCodeExecutorPool : IDisposable
{
IDynamicCodeExecutor GetOrCreate(DynamicCodeSecurityLevel securityLevel);
}
}

View File

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

View File

@@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
internal interface IShutdownAwareDynamicCodeExecutionRuntime : IDynamicCodeExecutionRuntime
{
Task ShutdownAsync();
}
}

View File

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