-
Notifications
You must be signed in to change notification settings - Fork 803
feat: add command gateway for multi-agent concurrent access #826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Lint111
wants to merge
33
commits into
CoplayDev:beta
Choose a base branch
from
Lint111:feature/command-gateway
base: beta
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+4,721
−27
Open
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
6c8d6cf
feat(tools): add ExecutionTier enum and annotate built-in tools
Lint111 2891b10
feat(tools): add CommandClassifier for action-level tier overrides
Lint111 3b8ee93
feat(tools): add BatchJob model and TicketStore for job lifecycle
Lint111 e9933f6
feat(tools): add tier-aware CommandQueue and gateway state
Lint111 e7e3e56
feat(tools): extend BatchExecute with async gateway path
Lint111 13e2dca
feat(tools): add PollJob and QueueStatus tools
Lint111 09d40c9
test(tools): add unit tests for command gateway components
Lint111 504bdcf
fix(tools): add missing using directive for IMcpResponse in CommandQueue
Lint111 15cd6c8
chore(tools): add Unity meta files for command gateway scripts
Lint111 255ef84
docs: add gateway async-aware blocking design
Lint111 7d93dcc
docs: add gateway async-awareness implementation plan
Lint111 fd83cfa
feat(tools): add CausesDomainReload to CommandClassifier
Lint111 8d66bbc
feat(tools): add CausesDomainReload property to BatchCommand and Batc…
Lint111 6079cc5
feat(tools): propagate CausesDomainReload through queue submission
Lint111 4215cd2
feat(tools): add domain-reload guard to ProcessTick
Lint111 d08a017
feat(tools): add blocked_by reason to poll_job response
Lint111 a4842ff
feat(tools): persist gateway queue state across domain reloads
Lint111 8d9fcae
fix(gateway): hold heavy slot while editor busy from async side-effects
Lint111 db95bd3
feat(gateway): add compressed queue_status tool for batch status checks
Lint111 10cee37
docs: add queue summary UI design doc
Lint111 9bc09a7
docs: add queue summary UI implementation plan
Lint111 c652036
feat(gateway): expose GetAllJobs on CommandQueue for UI consumption
Lint111 9f9cae2
feat(gateway-ui): add McpQueueSection UXML layout
Lint111 6805bb6
feat(gateway-ui): add McpQueueSection USS styles
Lint111 c22369e
feat(gateway-ui): add McpQueueSection controller with status bar and …
Lint111 a87cc73
feat(gateway-ui): wire Queue tab into MCP editor window with 1s auto-…
Lint111 12f297e
fix(gateway-ui): add Queue UXML/USS to UIAssetSync for WSL compatibility
Lint111 7ff09e1
feat(gateway): auto-cleanup terminal jobs on poll with optional logging
Lint111 bed28a3
chore(gateway): stage meta files and WSL asset path workaround
Lint111 5e5cae7
feat(gateway): add logging settings box to Queue tab
Lint111 aac1d8f
fix(gateway): keep heavy jobs Running while async side-effects active
Lint111 3c70b86
fix(gateway): restore domain-reload jobs as Done after successful reload
Lint111 ff87441
Update docs/plans/2026-02-25-queue-summary-ui-design.md
Lint111 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| using UnityEditor; | ||
| using UnityEngine; | ||
| using System.IO; | ||
|
|
||
| namespace MCPForUnity.Editor.Helpers | ||
| { | ||
| /// <summary> | ||
| /// Automatically copies UXML and USS files from WSL package directories to a local | ||
| /// <c>Assets/MCPForUnityUI/</c> folder on every domain reload, preserving directory structure. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// <b>Problem:</b> Unity's UXML/USS importer on Windows cannot properly parse files | ||
| /// when packages live on a WSL2 filesystem (UNC paths like <c>\\wsl$\...</c>). The | ||
| /// VisualTreeAsset loads but CloneTree produces an empty tree. | ||
| /// </para> | ||
| /// <para> | ||
| /// <b>Solution:</b> On startup, this class copies all UI asset files to | ||
| /// <c>Assets/MCPForUnityUI/</c> and <see cref="AssetPathUtility.GetMcpPackageRootPath"/> | ||
| /// returns this fallback path when WSL is detected. | ||
| /// </para> | ||
| /// </remarks> | ||
| [InitializeOnLoad] | ||
| static class UIAssetSync | ||
| { | ||
| /// <summary>Destination folder under the Unity project for synced UI assets.</summary> | ||
| internal const string SyncedBasePath = "Assets/MCPForUnityUI"; | ||
|
|
||
| /// <summary> | ||
| /// Relative paths from package root to UXML and USS files that need syncing. | ||
| /// </summary> | ||
| private static readonly string[] k_UIAssetPaths = | ||
| { | ||
| "Editor/Windows/MCPForUnityEditorWindow.uxml", | ||
| "Editor/Windows/MCPForUnityEditorWindow.uss", | ||
| "Editor/Windows/MCPSetupWindow.uxml", | ||
| "Editor/Windows/MCPSetupWindow.uss", | ||
| "Editor/Windows/EditorPrefs/EditorPrefItem.uxml", | ||
| "Editor/Windows/EditorPrefs/EditorPrefsWindow.uxml", | ||
| "Editor/Windows/EditorPrefs/EditorPrefsWindow.uss", | ||
| "Editor/Windows/Components/Common.uss", | ||
| "Editor/Windows/Components/Connection/McpConnectionSection.uxml", | ||
| "Editor/Windows/Components/ClientConfig/McpClientConfigSection.uxml", | ||
| "Editor/Windows/Components/Validation/McpValidationSection.uxml", | ||
| "Editor/Windows/Components/Advanced/McpAdvancedSection.uxml", | ||
| "Editor/Windows/Components/Tools/McpToolsSection.uxml", | ||
| "Editor/Windows/Components/Resources/McpResourcesSection.uxml", | ||
| "Editor/Windows/Components/Queue/McpQueueSection.uxml", | ||
| "Editor/Windows/Components/Queue/McpQueueSection.uss", | ||
| }; | ||
|
|
||
| static UIAssetSync() | ||
| { | ||
| if (!NeedsSync()) | ||
| return; | ||
|
|
||
| string packageRoot = GetPackagePhysicalRoot(); | ||
| if (string.IsNullOrEmpty(packageRoot)) | ||
| return; | ||
|
|
||
| bool anyUpdated = false; | ||
|
|
||
| foreach (string relativePath in k_UIAssetPaths) | ||
| { | ||
| string sourcePath = Path.Combine(packageRoot, relativePath); | ||
| if (!File.Exists(sourcePath)) | ||
| continue; | ||
|
|
||
| string sourceContent = File.ReadAllText(sourcePath); | ||
|
|
||
| string destPath = Path.GetFullPath(Path.Combine(SyncedBasePath, relativePath)); | ||
Lint111 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| string destDir = Path.GetDirectoryName(destPath); | ||
|
|
||
| if (!string.IsNullOrEmpty(destDir) && !Directory.Exists(destDir)) | ||
| Directory.CreateDirectory(destDir); | ||
|
|
||
| if (File.Exists(destPath) && File.ReadAllText(destPath) == sourceContent) | ||
| continue; | ||
|
|
||
| File.WriteAllText(destPath, sourceContent); | ||
| Debug.Log($"[UIAssetSync] Updated {relativePath}"); | ||
| anyUpdated = true; | ||
| } | ||
Lint111 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (anyUpdated) | ||
| AssetDatabase.Refresh(); | ||
| } | ||
Lint111 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Returns true when the MCP package lives on a WSL UNC path and Unity runs on Windows. | ||
| /// </summary> | ||
| internal static bool NeedsSync() | ||
| { | ||
| if (Application.platform != RuntimePlatform.WindowsEditor) | ||
| return false; | ||
|
|
||
| string packageRoot = GetPackagePhysicalRoot(); | ||
| if (string.IsNullOrEmpty(packageRoot)) | ||
| return false; | ||
|
|
||
| return packageRoot.StartsWith(@"\\wsl", System.StringComparison.OrdinalIgnoreCase); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the physical (filesystem) root path of the MCP package. | ||
| /// </summary> | ||
| private static string GetPackagePhysicalRoot() | ||
| { | ||
| var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly( | ||
| typeof(UIAssetSync).Assembly); | ||
| if (packageInfo != null && !string.IsNullOrEmpty(packageInfo.resolvedPath)) | ||
| return packageInfo.resolvedPath; | ||
|
|
||
| // Fallback: resolve the virtual asset path | ||
| if (packageInfo != null && !string.IsNullOrEmpty(packageInfo.assetPath)) | ||
| return Path.GetFullPath(packageInfo.assetPath); | ||
|
|
||
| return null; | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using Newtonsoft.Json.Linq; | ||
|
|
||
| namespace MCPForUnity.Editor.Tools | ||
| { | ||
| public enum JobStatus { Queued, Running, Done, Failed, Cancelled } | ||
|
|
||
| /// <summary> | ||
| /// Represents a queued batch of MCP commands with ticket tracking. | ||
| /// </summary> | ||
| public class BatchJob | ||
| { | ||
| public string Ticket { get; set; } | ||
| public string Agent { get; set; } | ||
| public string Label { get; set; } | ||
| public bool Atomic { get; set; } | ||
| public ExecutionTier Tier { get; set; } | ||
| public bool CausesDomainReload { get; set; } | ||
| public JobStatus Status { get; set; } = JobStatus.Queued; | ||
|
|
||
| public List<BatchCommand> Commands { get; set; } = new(); | ||
| public List<object> Results { get; set; } = new(); | ||
| public int CurrentIndex { get; set; } | ||
| public string Error { get; set; } | ||
|
|
||
| public DateTime CreatedAt { get; set; } = DateTime.UtcNow; | ||
| public DateTime? CompletedAt { get; set; } | ||
|
|
||
| public int UndoGroup { get; set; } = -1; | ||
| } | ||
|
|
||
| public class BatchCommand | ||
| { | ||
| public string Tool { get; set; } | ||
| public JObject Params { get; set; } | ||
| public ExecutionTier Tier { get; set; } | ||
| public bool CausesDomainReload { get; set; } | ||
| } | ||
|
|
||
| public class AgentStats | ||
| { | ||
| public int Active { get; set; } | ||
| public int Queued { get; set; } | ||
| public int Completed { get; set; } | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| using Newtonsoft.Json.Linq; | ||
|
|
||
| namespace MCPForUnity.Editor.Tools | ||
| { | ||
| /// <summary> | ||
| /// Refines a tool's ExecutionTier based on action-level parameters. | ||
| /// Tools declare a base tier via [McpForUnityTool(Tier=...)]; this classifier | ||
| /// can promote or demote based on specific action strings or param values. | ||
| /// </summary> | ||
| public static class CommandClassifier | ||
| { | ||
| /// <summary> | ||
| /// Classify a single command. Returns the effective tier after action-level overrides. | ||
| /// </summary> | ||
| public static ExecutionTier Classify(string toolName, ExecutionTier attributeTier, JObject @params) | ||
| { | ||
| if (@params == null) return attributeTier; | ||
|
|
||
| string action = @params.Value<string>("action"); | ||
|
|
||
| return toolName switch | ||
| { | ||
| "manage_scene" => ClassifyManageScene(action, attributeTier), | ||
| "refresh_unity" => ClassifyRefreshUnity(@params, attributeTier), | ||
| "manage_editor" => ClassifyManageEditor(action, attributeTier), | ||
| _ => attributeTier | ||
| }; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Classify a batch of commands. Returns the highest (most restrictive) tier. | ||
| /// </summary> | ||
| public static ExecutionTier ClassifyBatch( | ||
| (string toolName, ExecutionTier attributeTier, JObject @params)[] commands) | ||
| { | ||
| var max = ExecutionTier.Instant; | ||
| foreach (var (toolName, attributeTier, @params) in commands) | ||
| { | ||
| var tier = Classify(toolName, attributeTier, @params); | ||
| if (tier > max) max = tier; | ||
| } | ||
| return max; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns true if the given command would trigger a domain reload (compilation or play mode entry). | ||
| /// </summary> | ||
| public static bool CausesDomainReload(string toolName, JObject @params) | ||
| { | ||
| if (@params == null) return false; | ||
|
|
||
| return toolName switch | ||
| { | ||
| "refresh_unity" => @params.Value<string>("compile") != "none", | ||
| "manage_editor" => @params.Value<string>("action") == "play", | ||
| _ => false | ||
| }; | ||
| } | ||
|
|
||
| static ExecutionTier ClassifyManageScene(string action, ExecutionTier fallback) | ||
| { | ||
| return action switch | ||
| { | ||
| "get_hierarchy" or "get_active" or "get_build_settings" or "screenshot" | ||
| => ExecutionTier.Instant, | ||
| "create" or "load" or "save" | ||
| => ExecutionTier.Heavy, | ||
| _ => fallback | ||
| }; | ||
| } | ||
|
|
||
| static ExecutionTier ClassifyRefreshUnity(JObject @params, ExecutionTier fallback) | ||
| { | ||
| string compile = @params.Value<string>("compile"); | ||
| if (compile == "none") return ExecutionTier.Smooth; | ||
| return fallback; // Heavy by default | ||
| } | ||
|
|
||
| static ExecutionTier ClassifyManageEditor(string action, ExecutionTier fallback) | ||
| { | ||
| return action switch | ||
| { | ||
| "telemetry_status" or "telemetry_ping" => ExecutionTier.Instant, | ||
| "play" or "pause" or "stop" => ExecutionTier.Heavy, | ||
| _ => fallback | ||
| }; | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.