Skip to content

feat: add command gateway for multi-agent concurrent access#826

Open
Lint111 wants to merge 33 commits intoCoplayDev:betafrom
Lint111:feature/command-gateway
Open

feat: add command gateway for multi-agent concurrent access#826
Lint111 wants to merge 33 commits intoCoplayDev:betafrom
Lint111:feature/command-gateway

Conversation

@Lint111
Copy link

@Lint111 Lint111 commented Feb 25, 2026

Summary

  • Add tier-based command gateway enabling safe concurrent access from multiple MCP clients (e.g., parallel Claude Code agents)
  • Classify all 20+ built-in tools into Instant/Smooth/Heavy execution tiers with action-level overrides
  • Extend batch_execute with an async=true path that queues commands and returns tickets for polling
  • Add real-time Queue tab to MCP editor window with auto-refresh, status counters, and job list
  • Auto-cleanup completed jobs on poll with optional JSON-lines logging to configurable path

Motivation

When multiple AI agents interact with Unity simultaneously (e.g., parallel coding agents via Claude Code), they compete for the single-threaded editor. Without coordination, domain reloads from one agent break another's in-flight operations, causing cascading failures. This gateway provides safe concurrent access by:

  1. Classifying every tool invocation by its impact tier (read-only, main-thread write, or domain-reload)
  2. Queuing heavy operations exclusively — only one domain reload at a time
  3. Running read-only operations in parallel without blocking
  4. Giving each agent a ticket to track their batch's progress
  5. Persisting queue state across domain reloads via SessionState
  6. Auto-cleaning consumed tickets with optional audit logging

Architecture

Execution Tiers

Tier Description Examples
Instant Read-only, no side effects find_gameobjects, read_console, get_test_job
Smooth Main-thread mutations, no domain reload manage_gameobject, manage_material
Heavy Triggers domain reload or exclusive access manage_script.create, refresh_unity, run_tests

Action-Level Overrides

The CommandClassifier downgrades tool tiers based on specific actions. For example:

  • manage_scene is Heavy (can create scenes), but manage_scene.get_hierarchy is Instant
  • manage_asset is Smooth (can modify assets), but manage_asset.search is Instant

Queue Scheduling

ProcessTick (EditorApplication.update):
  1. If heavy job running -> wait
     - Hold heavy slot while editor busy (async ops like test runner)
     - One-frame cooldown after heavy completes
  2. If heavy queued + no smooth in flight -> start heavy (exclusive)
     - Guard: domain-reload jobs deferred while editor compiling/testing
  3. Otherwise -> start all queued smooth jobs (concurrent)

Domain Reload Safety

  • CausesDomainReload flag propagated from classifier through commands and jobs
  • Jobs flagged as domain-reload are blocked when editor is compiling or running tests
  • Queue state serialized to SessionState before assembly reload, restored after
  • Smart restore: jobs that caused the domain reload and had all commands dispatched are restored as Done (the reload IS the success signal); other interrupted jobs are marked Failed: "Interrupted by domain reload"

Job Lifecycle & Auto-Cleanup

Submit -> Queued -> Running -> Done/Failed
                                   |
                            poll_job returns result
                                   |
                     (optional) log to .jsonl file
                                   |
                          remove from queue
                                   |
                     next poll -> "not found"

Terminal jobs (Done, Failed, Cancelled) are removed from the store when polled.
If gateway job logging is enabled, job metadata is appended to a JSON-lines file before removal.
Uncollected jobs expire after 5 minutes as a safety net.

New Tools

  • poll_job — Poll a queued job by ticket. Returns status, progress, position in queue, blocked reason, and results when done. Auto-removes terminal jobs after returning data.
  • queue_status — Compressed queue summary: per-job status, active heavy ticket, queue depth, smooth in-flight count.

Queue Tab (Editor UI)

Real-time dashboard in the MCP For Unity editor window:

  • Status bar: Active heavy job, counters (queued/running/done/failed)
  • Job list: Ticket, status dot, agent, label, progress, blocked badge
  • Logging box: Toggle + configurable log file path with directory browser
  • Auto-refreshes every 1 second while visible

Files Changed (50 files, +4,721 lines)

New Production Code

  • ExecutionTier.cs — Enum + [ExecutionTier] attribute for tool registration
  • CommandClassifier.cs — Action-level tier overrides (tool + action -> effective tier)
  • BatchJob.cs — Job model with status lifecycle, undo group tracking, results collection
  • TicketStore.cs — Job storage with ticket-based lookup, expiry, removal, and domain-reload-aware restore
  • CommandQueue.cs — Tier-aware FIFO dispatcher with exclusive heavy scheduling, domain-reload guards, editor-busy hold
  • CommandGatewayState.cs[InitializeOnLoad] singleton wiring queue to EditorApplication.update with SessionState persistence
  • PollJob.cs — MCP tool for polling job status with auto-cleanup of terminal jobs
  • QueueStatus.cs — MCP tool for compressed queue introspection
  • GatewayJobLogger.cs — JSON-lines audit logger with configurable path via EditorPrefs

Queue Tab UI

  • McpQueueSection.uxml — Layout: status bar, logging box, job list header, dynamic rows, empty state
  • McpQueueSection.uss — Styles: status dots, columns, blocked badge, logging box, light/dark themes
  • McpQueueSection.cs — Controller: refresh from queue state, logging toggle/path wiring, file browser
  • UIAssetSync.cs — WSL UNC path workaround: copies UXML/USS to Assets/ on domain reload
  • AssetPathUtility.cs — Returns synced base path when WSL detected

Modified Production Code

  • BatchExecute.cs — Added async=true gateway path alongside existing synchronous path
  • McpForUnityToolAttribute.cs — Extended with ExecutionTier property
  • CommandRegistry.cs — Added GetToolTier() helper
  • MCPForUnityEditorWindow.cs — Wired Queue as 6th tab with 1s independent refresh timer
  • MCPForUnityEditorWindow.uxml — Added queue-tab toggle and queue-panel scroll view
  • EditorPrefKeys.cs — Added GatewayJobLogging and GatewayJobLogPath keys
  • 7 tool files — Added [ExecutionTier(Heavy)] annotations

Tests (5 test files, 47 tests)

  • ExecutionTierTests.cs — 5 tests: tier enum values, attribute defaults, attribute with tier parameter
  • CommandClassifierTests.cs — 13 tests: action-level classification, override logic, null/missing params, default fallback
  • TicketStoreTests.cs — 12 tests: job creation, ticket lookup, expiry cleanup, agent stats, queue ordering
  • CommandQueueTests.cs — 10 tests: submit, poll, cancel, queue depth, agent validation, heavy scheduling
  • BatchExecuteAsyncTests.cs — 7 tests: gateway state singleton, PollJob null/missing/invalid params, QueueStatus response, BatchExecute constants

Documentation

  • 2026-02-25-gateway-async-awareness-design.md — Domain-reload safety design doc
  • 2026-02-25-gateway-async-awareness-plan.md — Implementation plan for async guards
  • 2026-02-25-queue-summary-ui-design.md — Queue tab UI design doc
  • 2026-02-25-queue-summary-ui-plan.md — Queue tab implementation plan

Usage

Async batch submission

{
  "tool": "batch_execute",
  "params": {
    "async": true,
    "agent": "agent-1",
    "label": "Create player scripts",
    "atomic": true,
    "commands": [
      {"tool": "manage_script", "params": {"action": "create", "name": "Player", "path": "Assets/Scripts"}},
      {"tool": "manage_script", "params": {"action": "create", "name": "Enemy", "path": "Assets/Scripts"}}
    ]
  }
}

Poll for results

{"tool": "poll_job", "params": {"ticket": "t-000001"}}

Returns full results on completion, then auto-removes the ticket from the queue.

Check queue status

{"tool": "queue_status", "params": {}}

Job log output (when enabled)

{"ticket":"t-000010","agent":"agent-1","label":"run-tests","tier":"heavy","status":"done","created_at":"2026-02-25T11:09:14Z","completed_at":"2026-02-25T11:09:14Z","command_count":1,"tools":["run_tests"]}

Test Plan

  • All 47 new unit tests pass (MCP gateway test suite)
  • Existing MCP test suite passes with 0 regressions
  • Compile clean (0 errors)
  • Manual: async batch submission from MCP client (Instant inline, Heavy queued)
  • Manual: poll_job lifecycle — queued -> running -> done -> auto-removed
  • Manual: job logging to .jsonl verified with 3 tickets
  • Manual: Queue tab renders real-time status with 1s auto-refresh
  • Manual: logging toggle and path browser functional in Queue tab
  • Manual: WSL asset sync copies Queue UXML/USS on domain reload
  • Manual: concurrent agents — Agent 1 run_tests stays "running" during async test execution, Agent 2 refresh_unity blocked with tests_running until Agent 1 completes, then executes
  • Manual: heavy job async hold — jobs stay Running while editor busy, transition to Done only after side-effects settle
  • Manual: domain-reload restore — refresh_unity job correctly shows Done (not Failed) after the domain reload it triggered

Summary by Sourcery

Introduce a tier-aware command gateway with async batch execution, queue persistence, and an in-editor Queue tab for monitoring and logging multi-agent MCP jobs.

New Features:

  • Add an async batch_execute path that classifies commands into execution tiers, queues non-instant work, and returns tickets for later polling.
  • Expose poll_job and queue_status tools to inspect queued jobs, including blocking reasons and compressed queue summaries.
  • Add a Queue tab to the MCP For Unity editor window that shows real-time job status, active heavy work, and queue metrics with auto-refresh.
  • Log completed gateway jobs to a configurable JSON-lines file for auditing.

Enhancements:

  • Classify built-in tools with an ExecutionTier attribute and refine tiers at action level via CommandClassifier, including domain-reload detection.
  • Extend the command registry and batch job model to carry execution tiers and domain-reload metadata through the dispatch pipeline.
  • Throttle editor polling and add async-aware handling of heavy jobs so domain-reload operations are deferred while tests or compilation are running.
  • Persist command queue state across domain reloads via SessionState, restoring queued jobs safely and marking interrupted work appropriately.
  • Handle WSL UNC-path limitations by syncing UI assets into the project so Queue UI UXML/USS load reliably.

Documentation:

  • Document the design and implementation plans for async-aware gateway blocking and the queue summary UI, including domain-reload safety and editor integration.

Tests:

  • Add unit tests for execution tier semantics, command classification, ticket store behavior, queue scheduling/guards, async batch handling, and new tools’ responses.

Summary by CodeRabbit

  • New Features

    • Asynchronous command execution with tiered queueing, persistence, and real-time status
    • New Queue tab in the editor showing job list, progress, active-heavy indicator and blocked reasons
    • Job logging to JSONL for completed gateway jobs
    • Automatic UI asset synchronization for WSL-based setups
  • Tests

    • Extensive unit tests for async execution, queue behavior, classification, persistence, and polling
  • Documentation

    • Design docs for async gateway, domain-reload guards, persistence, and Queue UI

Lint111 and others added 30 commits February 25, 2026 10:50
Add ExecutionTier enum (Instant/Smooth/Heavy) to classify tool execution
cost. Extend McpForUnityToolAttribute with Tier property (default Smooth).
Annotate built-in tools: Heavy for manage_script, manage_shader,
refresh_unity, run_tests. Instant for find_gameobjects, read_console,
get_test_job.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CommandClassifier.Classify() inspects action parameters to override
the declared tool tier (e.g., manage_scene.get_hierarchy -> Instant,
manage_scene.load -> Heavy). ClassifyBatch() returns the highest tier
across a set of commands for queue scheduling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BatchJob tracks ticket, agent, commands, results, and status.
TicketStore manages CRUD operations, ticket generation, expiry cleanup,
and per-agent statistics. Foundation for the command gateway queue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CommandQueue provides FIFO dispatch with tier awareness: instant jobs
execute inline, smooth jobs run when no heavy is active, heavy jobs
get exclusive access. CommandGatewayState hooks into EditorApplication
.update for per-frame queue processing. CommandRegistry extended with
GetToolTier() and tier storage in HandlerInfo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When async=true is passed, BatchExecute now routes through
CommandGatewayState.Queue instead of executing inline. Returns
a ticket for non-instant batches (poll via poll_job) or results
directly for instant batches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PollJob (Instant tier) returns job status with position/progress/results.
QueueStatus (Instant tier) returns queue depth, active heavy job info,
smooth in-flight count, and per-agent statistics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
45 tests covering:
- ExecutionTier enum ordering and attribute defaults (5)
- CommandClassifier action-level overrides and batching (13)
- TicketStore CRUD, expiry, agent stats (10)
- CommandQueue submit, poll, cancel, queue depth (10)
- BatchExecute async path, PollJob, QueueStatus integration (7)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Design for two fixes to the command gateway:
1. Domain-reload-causing operations (refresh_unity, play mode) are
   held in queue while tests are running or compilation is active
2. Queue state persists across domain reloads via SessionState

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7-task TDD plan for making the command gateway aware of async Unity
operations (test runs, compilation) so domain-reload-causing commands
are held until safe, and queue state survives domain reloads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only refresh_unity (compile!=none) and manage_editor (play) trigger
domain reload. Script/shader file ops are just disk I/O.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hJob

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Domain-reload-causing jobs (refresh_unity compile, manage_editor play)
are held in queue while TestJobManager.HasRunningJob or
EditorApplication.isCompiling is true. Non-reload heavy jobs proceed
normally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a queued domain-reload job is blocked by editor activity (tests
running, compiling), the poll_job response now includes a blocked_by
field indicating the reason. Non-reload jobs return null.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TicketStore now supports ToJson/FromJson serialization. Running jobs
are marked failed on restore (interrupted by reload), queued jobs
survive and re-enter the heavy queue. CommandGatewayState hooks
AssemblyReloadEvents to persist via SessionState.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When run_tests completes instantly (fire-and-forget) but the TestRunner
is still executing, the heavy slot was released immediately, allowing
domain-reload jobs like refresh_unity to be dequeued before the guard
could catch them.

Three fixes:
1. Hold the heavy slot after batch completion while IsEditorBusy() is
   true — prevents next heavy dequeue until async operations settle
2. One-frame cooldown between heavy job completions — gives async state
   one editor frame to propagate before the guard check
3. Add TestRunStatus.IsRunning to IsEditorBusy predicate for defense-in-
   depth (covers manual UI test runs alongside TestJobManager tracking)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instant tier (never queued, non-blocking). Returns all tickets with
compact field names (t/s/a/l/p/b/e) for token-efficient batch polling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New Queue tab for MCP editor window with real-time job list,
status bar, and 1s auto-refresh. UIElements-based section component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6 tasks: expose GetAllJobs, UXML layout, USS styles, C# controller,
wire into editor window with 1s refresh, integration verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…job list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…refresh

Adds Queue as 6th tab in the MCP For Unity editor window. Loads
McpQueueSection UXML/USS/controller, wires tab toggle and panel
switching, adds independent 1-second refresh timer that only fires
while the Queue tab is visible (before the 2-second connection
status throttle).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Queue tab was blank because UIAssetSync didn't copy the new UXML/USS
files from WSL to Assets/MCPForUnityUI/. Added both files to the
sync list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an agent polls a completed/failed/cancelled job, the ticket is
removed from the queue after building the response. If the
GatewayJobLogging EditorPref is enabled, job data is serialized to
Logs/mcp-gateway-jobs.jsonl before removal.

- Add Remove(ticket) to TicketStore and CommandQueue
- Add GatewayJobLogger utility (JSON-lines file output)
- Add GatewayJobLogging EditorPref key
- Modify PollJob to auto-remove terminal jobs after response

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Unity .meta files for Queue UI components and UIAssetSync
- AssetPathUtility returns UIAssetSync.SyncedBasePath when WSL detected

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Toggle to enable/disable gateway job logging
- Text field showing current log file path (default: Logs/mcp-gateway-jobs.jsonl)
- Browse button opens file dialog to choose custom log path
- Path row hidden when logging is disabled
- GatewayJobLogger now supports configurable path via EditorPrefs
- Light/dark theme support for logging box

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lint111 and others added 2 commits February 25, 2026 13:22
Previously, a batch containing run_tests would show status "done"
immediately after the command returned, even though the test runner
was still executing asynchronously. Now:

- Heavy jobs stay in Running status if IsEditorBusy() is true after
  all commands dispatch
- ProcessTick transitions them to Done once the editor settles
- Remove() clears _activeHeavyTicket if the removed job was active
- ProcessTick handles null jobs (removed by auto-cleanup) gracefully

Tested: Agent 1 run_tests shows "running" during test execution,
transitions to "done" only after tests finish. Agent 2 refresh_unity
correctly blocked with "tests_running" until Agent 1 completes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Jobs that caused a domain reload (e.g., refresh_unity with compile) and
had all commands dispatched are now restored as Done instead of Failed
when the editor reloads. The reload IS the success signal for these jobs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 11:39
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 25, 2026

Reviewer's Guide

Implements a tier-aware command gateway with async batch execution, persistent job queue, and an in-editor Queue tab, including new tooling for job polling, queue inspection, domain-reload safety, and optional JSONL job logging.

Sequence diagram for async batch_execute submission via command gateway

sequenceDiagram
    actor Agent
    participant MCPClient
    participant BatchExecute
    participant CommandRegistry
    participant CommandClassifier
    participant CommandGatewayState
    participant CommandQueue

    Agent->>MCPClient: Send batch_execute(async=true, commands)
    MCPClient->>BatchExecute: HandleCommand(params)

    BatchExecute->>BatchExecute: Validate commands, atomic, agent, label
    BatchExecute->>CommandRegistry: GetToolTier(toolName) for each command
    CommandRegistry-->>BatchExecute: ExecutionTier

    loop For_each_command
        BatchExecute->>CommandClassifier: Classify(toolName, toolTier, cmdParams)
        CommandClassifier-->>BatchExecute: effectiveTier
        BatchExecute->>CommandClassifier: CausesDomainReload(toolName, cmdParams)
        CommandClassifier-->>BatchExecute: causesDomainReload
        BatchExecute->>BatchExecute: Build BatchCommand(Tool, Params, Tier, CausesDomainReload)
    end

    BatchExecute->>CommandGatewayState: Queue.Submit(agent, label, atomic, commands)
    CommandGatewayState->>CommandQueue: Submit(agent, label, atomic, commands)
    CommandQueue->>CommandQueue: ClassifyBatch(commands)
    CommandQueue->>CommandQueue: CreateJob + enqueue heavy if needed
    CommandGatewayState-->>BatchExecute: BatchJob(job)

    alt job.Tier == Instant
        BatchExecute->>CommandRegistry: InvokeCommandAsync for each command
        CommandRegistry-->>BatchExecute: results
        BatchExecute->>BatchExecute: job.Results, Status=Done
        BatchExecute-->>MCPClient: SuccessResponse(ticket, results)
    else Non_instant (Smooth or Heavy)
        BatchExecute->>CommandQueue: GetAheadOf(job.Ticket)
        CommandQueue-->>BatchExecute: jobsAhead
        BatchExecute-->>MCPClient: PendingResponse(ticket, status=queued, position, tier, agent, label)
    end

    MCPClient-->>Agent: Return ticket for later polling
Loading

Sequence diagram for poll_job lifecycle and auto-cleanup

sequenceDiagram
    actor Agent
    participant MCPClient
    participant PollJob
    participant CommandGatewayState
    participant CommandQueue
    participant TicketStore
    participant GatewayJobLogger

    Agent->>MCPClient: Send poll_job(ticket)
    MCPClient->>PollJob: HandleCommand(params)

    PollJob->>CommandGatewayState: Queue.Poll(ticket)
    CommandGatewayState->>CommandQueue: Poll(ticket)
    CommandQueue->>TicketStore: GetJob(ticket)
    TicketStore-->>CommandQueue: BatchJob(job)
    CommandQueue-->>PollJob: BatchJob(job)

    alt job.Status == Queued
        PollJob->>CommandQueue: GetAheadOf(ticket)
        CommandQueue-->>PollJob: aheadJobs
        PollJob->>CommandQueue: GetBlockedReason(ticket)
        CommandQueue-->>PollJob: blockedBy
        PollJob-->>MCPClient: PendingResponse(status=queued, position, blocked_by)
    else job.Status == Running
        PollJob-->>MCPClient: PendingResponse(status=running, progress)
    else job.Status == Done
        PollJob-->>MCPClient: SuccessResponse(status=done, results)
        PollJob->>GatewayJobLogger: Log(job) [if enabled]
        PollJob->>CommandGatewayState: Queue.Remove(ticket)
        CommandGatewayState->>CommandQueue: Remove(ticket)
        CommandQueue->>TicketStore: Remove(ticket)
        TicketStore-->>CommandQueue: removedJob
    else job.Status == Failed or Cancelled
        PollJob-->>MCPClient: ErrorResponse(status, error, results)
        PollJob->>GatewayJobLogger: Log(job) [if enabled]
        PollJob->>CommandGatewayState: Queue.Remove(ticket)
        CommandGatewayState->>CommandQueue: Remove(ticket)
        CommandQueue->>TicketStore: Remove(ticket)
        TicketStore-->>CommandQueue: removedJob
    end

    MCPClient-->>Agent: Present job status or results
Loading

Updated class diagram for the command gateway and queueing model

classDiagram
    direction LR

    class ExecutionTier {
        <<enum>>
        Instant
        Smooth
        Heavy
    }

    class JobStatus {
        <<enum>>
        Queued
        Running
        Done
        Failed
        Cancelled
    }

    class McpForUnityToolAttribute {
        +string CommandName
        +string PollAction
        +ExecutionTier Tier
    }

    class CommandClassifier {
        +ExecutionTier Classify(toolName, attributeTier, @params)
        +ExecutionTier ClassifyBatch(commands)
        +bool CausesDomainReload(toolName, @params)
    }

    class BatchCommand {
        +string Tool
        +JObject Params
        +ExecutionTier Tier
        +bool CausesDomainReload
    }

    class BatchJob {
        +string Ticket
        +string Agent
        +string Label
        +bool Atomic
        +ExecutionTier Tier
        +bool CausesDomainReload
        +JobStatus Status
        +List~BatchCommand~ Commands
        +List~object~ Results
        +int CurrentIndex
        +string Error
        +DateTime CreatedAt
        +DateTime CompletedAt
        +int UndoGroup
    }

    class AgentStats {
        +int Active
        +int Queued
        +int Completed
    }

    class TicketStore {
        -Dictionary~string,BatchJob~ _jobs
        -int _nextId
        +BatchJob CreateJob(agent, label, atomic, tier)
        +BatchJob GetJob(ticket)
        +BatchJob Remove(ticket)
        +void CleanExpired(expiry)
        +List~BatchJob~ GetQueuedJobs()
        +List~BatchJob~ GetRunningJobs()
        +Dictionary~string,AgentStats~ GetAgentStats()
        +List~BatchJob~ GetAllJobs()
        +int QueueDepth
        +string ToJson()
        +void FromJson(json)
    }

    class CommandQueue {
        -TicketStore _store
        -Queue~string~ _heavyQueue
        -List~string~ _smoothInFlight
        -string _activeHeavyTicket
        +Func~bool~ IsEditorBusy
        +bool HasActiveHeavy
        +int QueueDepth
        +int SmoothInFlight
        +BatchJob Submit(agent, label, atomic, commands)
        +BatchJob Poll(ticket)
        +bool Cancel(ticket, agent)
        +List~BatchJob~ GetAheadOf(ticket)
        +string GetBlockedReason(ticket)
        +BatchJob Remove(ticket)
        +object GetSummary()
        +List~BatchJob~ GetAllJobs()
        +object GetStatus()
        +string PersistToJson()
        +void RestoreFromJson(json)
        +void ProcessTick(executeCommand)
        -Task ExecuteJob(job, executeCommand)
    }

    class CommandGatewayState {
        <<static>>
        +CommandQueue Queue
        -const string SessionKey
        -static void OnUpdate()
    }

    class GatewayJobLogger {
        <<static>>
        +string DefaultLogPath
        +bool IsEnabled
        +string LogPath
        +void Log(job)
    }

    class PollJob {
        <<tool>>
        +object HandleCommand(@params)
    }

    class QueueStatus {
        <<tool>>
        +object HandleCommand(@params)
    }

    class BatchExecute {
        +Task~object~ HandleCommand(@params)
        -object HandleAsyncSubmit(@params, commandsToken)
    }

    class CommandRegistry {
        +ExecutionTier GetToolTier(commandName)
        +Task~object~ InvokeCommandAsync(commandName, @params)
    }

    class McpQueueSection {
        +VisualElement Root
        +McpQueueSection(root)
        +void Refresh()
        -void CacheUIElements()
        -void SetupLoggingControls()
        -void UpdateStatusBar(queue, allJobs)
        -void UpdateJobList(allJobs, queue)
        -VisualElement CreateJobRow(job, queue)
    }

    class MCPForUnityEditorWindow {
        -McpQueueSection queueSection
        -ToolbarToggle queueTabToggle
        -VisualElement queuePanel
        -double _lastQueueRefreshTime
        -ActivePanel _currentPanel
        -void OnEditorUpdate()
        -void SetupTabs()
        -void SwitchPanel(panel)
        -void CreateGUI()
    }

    class ExecutionTierTests
    class CommandClassifierTests
    class CommandQueueTests
    class TicketStoreTests
    class BatchExecuteAsyncTests

    McpForUnityToolAttribute --> ExecutionTier
    CommandClassifier --> ExecutionTier
    CommandClassifier --> BatchCommand
    BatchJob --> ExecutionTier
    BatchJob --> JobStatus
    BatchJob --> BatchCommand
    TicketStore --> BatchJob
    TicketStore --> AgentStats
    CommandQueue --> TicketStore
    CommandQueue --> BatchJob
    CommandQueue --> BatchCommand
    CommandGatewayState --> CommandQueue
    GatewayJobLogger --> BatchJob
    PollJob --> CommandGatewayState
    PollJob --> GatewayJobLogger
    QueueStatus --> CommandGatewayState
    BatchExecute --> CommandRegistry
    BatchExecute --> CommandClassifier
    BatchExecute --> CommandGatewayState
    McpQueueSection --> CommandGatewayState
    MCPForUnityEditorWindow --> McpQueueSection

    ExecutionTierTests --> ExecutionTier
    CommandClassifierTests --> CommandClassifier
    CommandQueueTests --> CommandQueue
    TicketStoreTests --> TicketStore
    BatchExecuteAsyncTests --> BatchExecute
Loading

Updated class diagram for the Queue tab UI components

classDiagram
    direction LR

    class MCPForUnityEditorWindow {
        -McpQueueSection queueSection
        -ToolbarToggle queueTabToggle
        -VisualElement queuePanel
        -double _lastQueueRefreshTime
        -double QueueRefreshIntervalSeconds
        -double _lastEditorUpdateTime
        -double EditorUpdateIntervalSeconds
        -ActivePanel _currentPanel
        +void CreateGUI()
        -void SetupTabs()
        -void SwitchPanel(panel)
        -void OnEditorUpdate()
    }

    class McpQueueSection {
        +VisualElement Root
        -Label heavyTicketLabel
        -Label queuedCount
        -Label runningCount
        -Label doneCount
        -Label failedCount
        -VisualElement jobListContainer
        -VisualElement jobListHeader
        -Label emptyLabel
        -Toggle loggingToggle
        -TextField logPathField
        -Button browseLogPathBtn
        -VisualElement loggingPathRow
        +McpQueueSection(root)
        +void Refresh()
        -void CacheUIElements()
        -void SetupLoggingControls()
        -void UpdateLoggingPathVisibility()
        -void UpdateStatusBar(queue, allJobs)
        -void UpdateJobList(allJobs, queue)
        -VisualElement CreateJobRow(job, queue)
        -string Truncate(text, maxLength)
    }

    class GatewayJobLogger {
        <<static>>
        +string DefaultLogPath
        +bool IsEnabled
        +string LogPath
        +void Log(job)
    }

    class CommandGatewayState {
        <<static>>
        +CommandQueue Queue
    }

    class CommandQueue {
        +List~BatchJob~ GetAllJobs()
        +bool HasActiveHeavy
        +int QueueDepth
        +int SmoothInFlight
        +string GetBlockedReason(ticket)
        +Func~bool~ IsEditorBusy
    }

    class TicketStore {
        +List~BatchJob~ GetAllJobs()
    }

    class BatchJob {
        +string Ticket
        +string Agent
        +string Label
        +ExecutionTier Tier
        +bool CausesDomainReload
        +JobStatus Status
        +List~BatchCommand~ Commands
        +List~object~ Results
        +int CurrentIndex
        +string Error
    }

    class UIAssetSync {
        <<static>>
        +string SyncedBasePath
        +bool NeedsSync()
    }

    class AssetPathUtility {
        +string GetMcpPackageRootPath()
    }

    MCPForUnityEditorWindow --> McpQueueSection
    MCPForUnityEditorWindow --> UIAssetSync
    UIAssetSync --> AssetPathUtility
    McpQueueSection --> CommandGatewayState
    CommandGatewayState --> CommandQueue
    CommandQueue --> TicketStore
    CommandQueue --> BatchJob
    McpQueueSection --> GatewayJobLogger
Loading

File-Level Changes

Change Details Files
Introduce tiered execution model and async-aware command gateway with persistent queue and domain-reload safety.
  • Add ExecutionTier enum and CommandClassifier to determine tool tiers and whether commands cause domain reloads, including batch-level classification.
  • Implement BatchJob/BatchCommand models and TicketStore with JSON serialization/deserialization and SessionState-based persistence across domain reloads.
  • Add CommandQueue and CommandGatewayState to schedule jobs by tier, coordinate with editor busy state (tests/compilation), and restore queue/IDs after reload.
MCPForUnity/Editor/Tools/ExecutionTier.cs
MCPForUnity/Editor/Tools/CommandClassifier.cs
MCPForUnity/Editor/Tools/BatchJob.cs
MCPForUnity/Editor/Tools/TicketStore.cs
MCPForUnity/Editor/Tools/CommandQueue.cs
MCPForUnity/Editor/Tools/CommandGatewayState.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecutionTierTests.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandClassifierTests.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/TicketStoreTests.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandQueueTests.cs
docs/plans/2026-02-25-gateway-async-awareness-design.md
docs/plans/2026-02-25-gateway-async-awareness-plan.md
Extend batch_execute with async submission path and new MCP tools for polling jobs and querying queue state, plus job logging.
  • Add async=true path in BatchExecute to classify commands, submit jobs to CommandQueue, and either run pure-Instant batches inline or return PendingResponse tickets for queued work.
  • Expose tool execution tiers via McpForUnityToolAttribute and CommandRegistry, and annotate existing tools with Instant/Heavy classifications.
  • Introduce PollJob and QueueStatus tools plus GatewayJobLogger to support ticket polling, compressed queue snapshots, and optional JSON-lines logging controlled via EditorPrefs.
MCPForUnity/Editor/Tools/BatchExecute.cs
MCPForUnity/Editor/Tools/McpForUnityToolAttribute.cs
MCPForUnity/Editor/Tools/CommandRegistry.cs
MCPForUnity/Editor/Constants/EditorPrefKeys.cs
MCPForUnity/Editor/Tools/FindGameObjects.cs
MCPForUnity/Editor/Tools/GetTestJob.cs
MCPForUnity/Editor/Tools/ManageScript.cs
MCPForUnity/Editor/Tools/ManageShader.cs
MCPForUnity/Editor/Tools/ReadConsole.cs
MCPForUnity/Editor/Tools/RefreshUnity.cs
MCPForUnity/Editor/Tools/RunTests.cs
MCPForUnity/Editor/Tools/PollJob.cs
MCPForUnity/Editor/Tools/QueueStatus.cs
MCPForUnity/Editor/Tools/GatewayJobLogger.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/BatchExecuteAsyncTests.cs
Add an in-editor Queue tab UI that visualizes gateway jobs and logging state, including WSL-safe asset loading.
  • Create McpQueueSection UXML/USS layout and C# controller to display active heavy job, counters, and a live-updating job list with status dots, progress, and blocked badges, plus logging controls.
  • Integrate Queue tab into MCPForUnityEditorWindow with new toolbar toggle, panel, UXML hooks, stylesheet loading, and a 1-second per-tab refresh loop.
  • Add UIAssetSync and AssetPathUtility hook so UXML/USS assets are mirrored into Assets/ on WSL UNC paths, ensuring reliable UI loading.
MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.uxml
MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.uss
MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.cs
MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.uxml
MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
MCPForUnity/Editor/Helpers/UIAssetSync.cs
MCPForUnity/Editor/Helpers/AssetPathUtility.cs
docs/plans/2026-02-25-queue-summary-ui-design.md
docs/plans/2026-02-25-queue-summary-ui-plan.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In CommandQueue, GetBlockedReason reaches into TestJobManager/TestRunStatus/EditorApplication directly even though IsEditorBusy is already injected; consider routing both the boolean and reason through a single delegate/interface so the queue remains decoupled from editor/test services and easier to fake in tests.
  • The HandleAsyncSubmit path in BatchExecute runs "instant" commands synchronously on the editor thread via InvokeCommandAsync(...).GetAwaiter().GetResult(), which risks UI stalls or deadlocks for long-running async tools; consider making this path fully async (using await) or explicitly constraining/documenting the tools that can be treated as Instant.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `CommandQueue`, `GetBlockedReason` reaches into `TestJobManager`/`TestRunStatus`/`EditorApplication` directly even though `IsEditorBusy` is already injected; consider routing both the boolean and reason through a single delegate/interface so the queue remains decoupled from editor/test services and easier to fake in tests.
- The `HandleAsyncSubmit` path in `BatchExecute` runs "instant" commands synchronously on the editor thread via `InvokeCommandAsync(...).GetAwaiter().GetResult()`, which risks UI stalls or deadlocks for long-running async tools; consider making this path fully async (using `await`) or explicitly constraining/documenting the tools that can be treated as `Instant`.

## Individual Comments

### Comment 1
<location path="TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/BatchExecuteAsyncTests.cs" line_range="59" />
<code_context>
+        [Test]
+        public void BatchExecuteMaxCommands_DefaultIs25()
</code_context>
<issue_to_address>
**suggestion (testing):** Add targeted tests for the new async batch submission path (instant vs queued behaviour and error conditions).

The new `BatchExecute.HandleAsyncSubmit` path is core to this gateway, but it isn’t covered by the current tests. Please add tests that drive the async path via the `batch_execute` tool, including:

- An `async=true` batch with only `ExecutionTier.Instant` commands, asserting a `SuccessResponse` (`status="done"`, `ticket`, and populated `results`) and that the `BatchJob` is completed rather than queued.
- An `async=true` batch that includes `Smooth`/`Heavy` commands, asserting a `PendingResponse` (`ticket`, `status="queued"`, `position`, `tier`, `agent`, `label`, `atomic`), and ideally verifying the queued job state (e.g. via `CommandGatewayState.Queue.Poll(ticket)`).
- Error cases: only invalid commands (e.g. missing `tool`) should return `ErrorResponse("No valid commands in async batch.")`, and a failing command in an atomic instant batch should fail with partial `results` and the `ticket` present in the error data.

This will exercise the new async semantics end-to-end and validate the instant-vs-queued behaviour.
</issue_to_address>

### Comment 2
<location path="docs/plans/2026-02-25-gateway-async-awareness-plan.md" line_range="394-395" />
<code_context>
+                    }
+
+                    _activeHeavyTicket = ticket;
+                    _ = ExecuteJob(job, executeCommand);
+                    return;
+                }
</code_context>
<issue_to_address>
**nitpick (typo):** Clarify grammar in the inline comment about re-enqueuing peeked items.

The inline comment `// Re-enqueue any remaining peeked items were already handled by the loop` is grammatically awkward. Please rephrase, e.g. `// Remaining peeked items are already handled by the loop` or similar.

Suggested implementation:

```
        if (job == null || job.Status == JobStatus.Cancelled) continue;

        // Guard: domain-reload jobs must wait when editor is busy
        if (job.CausesDomainReload && editorBusy)
        {
            _heavyQueue.Enqueue(ticket); // put back at end
            continue;
        }

        // Remaining peeked items are already handled by the loop
        _activeHeavyTicket = ticket;

```

If the original inline comment you referenced (`// Re-enqueue any remaining peeked items were already handled by the loop`) appears elsewhere in the file or in related files, apply a similar replacement there:

<<<<<<< SEARCH
// Re-enqueue any remaining peeked items were already handled by the loop
=======
// Remaining peeked items are already handled by the loop
>>>>>>> REPLACE

Adjust capitalization or wording to match your team's commenting style if needed.
</issue_to_address>

### Comment 3
<location path="docs/plans/2026-02-25-queue-summary-ui-design.md" line_range="34-36" />
<code_context>
+
+### Status bar
+- Active heavy ticket with agent + label (or "No active heavy job")
+- Three counters: Queued / Running / Done+Failed
+- Uses existing `.section` and `.setting-row` styles
+
</code_context>
<issue_to_address>
**suggestion:** Align the documented counter list with the implemented four counters.

This line still refers to “Three counters: Queued / Running / Done+Failed”, but the UI and UXML/USS show four separate counters: queued, running, done, and failed. Please either list all four here or reword this bullet so it accurately matches the final UI behaviour.

```suggestion
### Status bar
- Active heavy ticket with agent + label (or "No active heavy job")
- Four counters: Queued / Running / Done / Failed
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces a sophisticated command gateway system that enables safe concurrent access to Unity from multiple MCP clients. The implementation addresses the critical problem of domain reloads interrupting parallel agent operations by classifying tools into execution tiers (Instant/Smooth/Heavy) and implementing intelligent queue scheduling with domain-reload awareness.

Changes:

  • Core gateway infrastructure with tier-based command classification, queue management, and SessionState persistence for domain-reload survival
  • New poll_job and queue_status MCP tools for asynchronous batch execution with ticket-based polling
  • Real-time Queue tab in the MCP editor window with auto-refresh, job logging, and status visualization
  • WSL compatibility workaround (UIAssetSync) for UXML/USS assets
  • Extended batch_execute with async=true parameter for non-blocking command submission

Reviewed changes

Copilot reviewed 50 out of 50 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
ExecutionTier.cs Enum defining three-tier classification system (Instant/Smooth/Heavy)
CommandClassifier.cs Action-level tier refinement logic with domain-reload detection
BatchJob.cs Job model with lifecycle tracking, undo support, and domain-reload flag
TicketStore.cs Job storage with ticket generation, expiry, and SessionState serialization
CommandQueue.cs Tier-aware FIFO dispatcher with exclusive heavy scheduling and editor-busy guards
CommandGatewayState.cs Singleton wiring queue to EditorApplication.update with persistence hooks
PollJob.cs MCP tool for polling job status with auto-cleanup of terminal jobs
QueueStatus.cs Compressed queue introspection tool
GatewayJobLogger.cs Optional JSON-lines audit logging with configurable path
McpQueueSection.uxml/uss/cs Queue tab UI with status bar, job list, and logging controls
BatchExecute.cs Extended with async gateway path alongside synchronous execution
UIAssetSync.cs WSL UNC path workaround for Unity's UXML/USS importer limitations
5 test files 47 unit tests covering classifier, queue, store, and async tools
Tool files (7) ExecutionTier annotations for proper queue dispatch

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Lint111
Copy link
Author

Lint111 commented Feb 25, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

Adds an async-aware, tiered batch execution system to the Unity editor MCP gateway: new ExecutionTier model, per-command classification (including domain-reload detection), a persistent command queue with ticketing, polling endpoints, gateway job logging, editor Queue UI, WSL UI-asset sync, and comprehensive tests.

Changes

Cohort / File(s) Summary
Execution Tier & Classifier
MCPForUnity/Editor/Tools/ExecutionTier.cs, MCPForUnity/Editor/Tools/CommandClassifier.cs, MCPForUnity/Editor/Tools/McpForUnityToolAttribute.cs
Adds ExecutionTier enum, CommandClassifier (per-command tier + domain-reload detection), and a Tier property on the tool attribute to declare execution tier.
Queue & Persistence Core
MCPForUnity/Editor/Tools/CommandQueue.cs, MCPForUnity/Editor/Tools/TicketStore.cs, MCPForUnity/Editor/Tools/BatchJob.cs, MCPForUnity/Editor/Tools/CommandGatewayState.cs
Implements a tier-aware CommandQueue, TicketStore JSON persistence/restore, BatchJob/BatchCommand models (with domain-reload flag), and CommandGatewayState singleton that persists/restores queue across domain reloads.
Batch Submission & Polling
MCPForUnity/Editor/Tools/BatchExecute.cs, MCPForUnity/Editor/Tools/PollJob.cs, MCPForUnity/Editor/Tools/QueueStatus.cs
Adds async submission path (HandleAsyncSubmit) to BatchExecute, integrates classifier, queues jobs; PollJob to poll job status (returns Pending/Success/Error) and QueueStatus for snapshot.
Command Registry & Tool Annotations
MCPForUnity/Editor/Tools/CommandRegistry.cs, various MCPForUnity/Editor/Tools/* tool files (e.g., FindGameObjects.cs, ManageScript.cs, RefreshUnity.cs, RunTests.cs, GetTestJob.cs, ReadConsole.cs)
Propagates per-tool ExecutionTier through registration and annotates many tools with appropriate Tier values (Instant, Smooth, Heavy). Adds GetToolTier API.
Gateway Job Logging & EditorPrefs
MCPForUnity/Editor/Constants/EditorPrefKeys.cs, MCPForUnity/Editor/Tools/GatewayJobLogger.cs
Adds two EditorPref keys and a GatewayJobLogger to emit completed BatchJob records to a JSONL file (configurable path and enabled flag).
WSL UI Asset Sync & AssetPath Utility
MCPForUnity/Editor/Helpers/UIAssetSync.cs, MCPForUnity/Editor/Helpers/AssetPathUtility.cs
New UIAssetSync copies UXML/USS assets from package root into Assets/MCPForUnityUI when running under WSL UNC paths; AssetPathUtility obtains synced path early when sync is active.
Queue UI & Editor Integration
MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.cs, MCPForUnity/Editor/Windows/Components/Queue/*.{uxml,uss}, MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs, MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.uxml
Adds a Queue tab and McpQueueSection UI (UXML+USS+controller) integrated into the main editor window with a 1s refresh throttle and controls for job-logging settings.
Async Queue Tests
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/*Tests.cs
Comprehensive tests for CommandClassifier, CommandQueue, TicketStore, BatchExecute async flows, PollJob, QueueStatus, and ExecutionTier behaviors.
Unity Metadata
MCPForUnity/Editor/**/**/*.meta, MCPForUnity/Editor/Windows/Components/Queue/*.meta
Unity .meta files for all newly added C#, UXML, and USS assets.

Sequence Diagram(s)

sequenceDiagram
    participant Client as MCP Client
    participant BatchExec as BatchExecute
    participant Classifier as CommandClassifier
    participant Queue as CommandQueue
    participant Store as TicketStore
    participant EditorLoop as Editor.update
    participant Poll as PollJob

    Client->>BatchExec: async submit(commands)
    BatchExec->>Classifier: classify each command
    Classifier-->>BatchExec: (tier, causesDomainReload)
    BatchExec->>Queue: Submit(agent,label,atomic,commands)
    Queue->>Store: CreateJob(...)
    Store-->>Queue: BatchJob(ticket)
    Queue-->>BatchExec: PendingResponse(ticket)
    Client->>Poll: poll_job(ticket)
    Poll->>Queue: Poll(ticket)
    Queue->>Store: GetJob(ticket)
    Store-->>Queue: BatchJob(status)
    Queue-->>Poll: Pending/Success/Error
    EditorLoop->>Queue: ProcessTick(executeCommand)
    Queue->>EditorLoop: executeCommand(tool,params)
    EditorLoop-->>Queue: command result
    Queue->>Store: Update job status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • msanatan

"🐰
I hopped through queues and tiers today,
Instant, Smooth, and Heavy on display.
Tickets saved, logs written line by line,
WSL assets synced — the UI looks fine.
Hooray — the gateway hums, all jobs in time!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature: introducing a command gateway for multi-agent concurrent access, which aligns with the primary objective of the changeset.
Description check ✅ Passed The description is comprehensive and well-structured, covering motivation, architecture, files changed, usage examples, and test plan. It addresses the template sections including type of change (new feature), specific changes, testing verification, and related context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
MCPForUnity/Editor/Tools/BatchExecute.cs (1)

35-66: 🛠️ Refactor suggestion | 🟠 Major

Use ToolParams for top-level batch parameter parsing.

Line 56 and Lines 250-253 parse tool inputs directly from JObject. Please switch these to ToolParams so required/optional validation is consistent across editor tools.

As per coding guidelines MCPForUnity/Editor/Tools/*.cs: Use ToolParams for consistent parameter validation in C# Editor tools with methods like GetInt(), RequireString().

Also applies to: 248-253

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/BatchExecute.cs` around lines 35 - 66, The top-level
parameter parsing in HandleCommand is using JObject.Value/ indexing directly;
replace this with a ToolParams instance (e.g. new ToolParams(`@params`)) and call
its helpers (GetInt(), GetBool() / GetString() / RequireString() as appropriate)
to read "async", "failFast", "parallel", "maxParallelism" and the "commands"
array so validation is consistent with other editor tools; specifically, change
the commandsToken extraction to use ToolParams to require/validate the
"commands" entry and replace usages of `@params.Value`<int?>("maxParallelism") and
`@params.Value`<bool?>("async") etc. with the matching ToolParams methods while
preserving the async vs legacy flow inside HandleCommand and when calling
HandleAsyncSubmit.
🟡 Minor comments (13)
docs/plans/2026-02-25-gateway-async-awareness-plan.md-90-94 (1)

90-94: ⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks (MD040).

Lines 90, 121, 886, 893, and 904 start unlabeled fences. Please add a language (text, bash, csharp, json, etc.) to keep markdownlint clean.

Suggested fix pattern
-```
+```text
 run_tests(mode=EditMode, test_names=[
   "MCPForUnity.Tests.Editor.CommandClassifierTests.CausesDomainReload_RefreshUnity_CompileRequest_ReturnsTrue"
 ])
-```
+```

Also applies to: 121-123, 886-889, 893-896, 904-933

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-25-gateway-async-awareness-plan.md` around lines 90 - 94,
The markdown contains unlabeled fenced code blocks (e.g., the run_tests(...)
snippet and other blocks referenced at lines 90, 121, 886, 893, 904) causing
MD040; update each triple-backtick fence to include an appropriate language
identifier (for the run_tests snippet use text or bash, and for other blocks
choose csharp/json/text as appropriate) so every code fence is labeled (e.g.,
```text or ```csharp) while keeping the block contents unchanged.
docs/plans/2026-02-25-gateway-async-awareness-plan.md-13-13 (1)

13-13: ⚠️ Potential issue | 🟡 Minor

Fix heading level jump to satisfy markdown linting.

Line 13 jumps from # to ###; use ## here to avoid MD001 violations.

Suggested fix
-### Task 1: Add `CausesDomainReload` to CommandClassifier
+## Task 1: Add `CausesDomainReload` to CommandClassifier
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-25-gateway-async-awareness-plan.md` at line 13, Heading
level jumps from H1 to H3 for the section titled "Task 1: Add
`CausesDomainReload` to CommandClassifier"; change the heading token from "###
Task 1: Add `CausesDomainReload` to CommandClassifier" to "## Task 1: Add
`CausesDomainReload` to CommandClassifier" so the document uses a proper H2 and
satisfies markdown lint MD001.
docs/plans/2026-02-25-gateway-async-awareness-design.md-27-31 (1)

27-31: ⚠️ Potential issue | 🟡 Minor

Specify a language on the fenced block to satisfy markdownlint.

Line 27 opens an unlabeled fence; add a language token (for example text).

Suggested fix
-```
+```text
 if job.CausesDomainReload
    AND (TestJobManager.HasRunningJob OR EditorApplication.isCompiling)
 → skip, keep queued
-```
+```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-25-gateway-async-awareness-design.md` around lines 27 -
31, The fenced code block containing the conditional snippet with symbols
job.CausesDomainReload, TestJobManager.HasRunningJob, and
EditorApplication.isCompiling is missing a language specifier; update the
opening fence to include a language token (for example "text") so the block
becomes ```text ... ``` to satisfy markdownlint while preserving the exact
contents of the snippet.
docs/plans/2026-02-25-queue-summary-ui-design.md-13-13 (1)

13-13: ⚠️ Potential issue | 🟡 Minor

Add language specifiers to fenced code blocks to resolve MD040 lint warnings.

Both ASCII-art/pseudo-code blocks are missing a language tag (e.g., ```text).

Also applies to: 53-53

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-25-queue-summary-ui-design.md` at line 13, Add explicit
language specifiers to the fenced code blocks containing ASCII-art/pseudo-code
(the two triple-backtick blocks flagged by MD040) by changing their opening
fences from ``` to a tagged form like ```text (or ```ascii) so the linter
recognizes them; update both code fences so each opening triple-backtick
includes the chosen language tag.
MCPForUnity/Editor/Tools/PollJob.cs-57-68 (1)

57-68: ⚠️ Potential issue | 🟡 Minor

Guard against null Commands in the Running branch.

job.Commands.Count and job.Commands.Count at Lines 59 and 65 will throw a NullReferenceException if Commands is ever null on a Running job. While the current execution flow guarantees it's set before a job enters Running, defensive guarding avoids a silent regression if that invariant is weakened later.

🛡️ Proposed fix
-    response = new PendingResponse(
-        $"Running command {job.CurrentIndex + 1}/{job.Commands.Count}.",
+    int total = job.Commands?.Count ?? 0;
+    response = new PendingResponse(
+        $"Running command {job.CurrentIndex + 1}/{total}.",
         pollIntervalSeconds: 1.0,
         data: new
         {
             ticket = job.Ticket,
             status = "running",
-            progress = $"{job.CurrentIndex + 1}/{job.Commands.Count}",
+            progress = $"{job.CurrentIndex + 1}/{total}",
             agent = job.Agent,
             label = job.Label
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/PollJob.cs` around lines 57 - 68, Guard against
job.Commands being null in the JobStatus.Running branch: before using
job.Commands.Count or constructing the progress string, check for null (or use a
safe fallback) and compute safeCount = job.Commands?.Count ?? 0 and safeIndex =
Math.Min(job.CurrentIndex + 1, safeCount); then build the PendingResponse using
safeCount and safeIndex for the message and progress fields (update the Running
case that creates the PendingResponse to reference these safe values rather than
job.Commands.Count directly).
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/TicketStoreTests.cs-113-121 (1)

113-121: ⚠️ Potential issue | 🟡 Minor

Potential flaky ordering — CreatedAt timestamps may collide.

GetQueuedJobs() sorts by CreatedAt. If both jobs are created in the same millisecond (common in unit tests), OrderBy is not guaranteed to be stable between runs, and the positional assertions on lines 119–120 can fail non-deterministically.

Consider setting CreatedAt explicitly or asserting on ticket presence without relying on index position:

🔧 Proposed fix
 [Test]
 public void GetQueuedJobs_OrderedByCreationTime()
 {
     var j1 = _store.CreateJob("a", "first", false, ExecutionTier.Heavy);
+    j1.CreatedAt = System.DateTime.UtcNow.AddSeconds(-1);
     var j2 = _store.CreateJob("b", "second", false, ExecutionTier.Smooth);
     var queued = _store.GetQueuedJobs();
     Assert.That(queued[0].Ticket, Is.EqualTo(j1.Ticket));
     Assert.That(queued[1].Ticket, Is.EqualTo(j2.Ticket));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/TicketStoreTests.cs`
around lines 113 - 121, The test GetQueuedJobs_OrderedByCreationTime is flaky
because CreateJob may assign identical CreatedAt timestamps; update the test to
avoid relying on millisecond ordering by either explicitly setting distinct
CreatedAt values on j1 and j2 after creation (e.g., j1.CreatedAt = now;
j2.CreatedAt = now.AddMilliseconds(1)) before calling _store.GetQueuedJobs(), or
change the assertions to check that both tickets are present without requiring
specific positions (e.g., Assert.That(queued.Select(q => q.Ticket),
Is.EquivalentTo(new[] { j1.Ticket, j2.Ticket }))); modify only the test logic
around CreateJob, j1/j2, and the assertions so GetQueuedJobs and _store remain
unchanged.
MCPForUnity/Editor/Tools/PollJob.cs-18-21 (1)

18-21: ⚠️ Potential issue | 🟡 Minor

Use RequireString() per the ToolParams coding guideline.

p.GetRequired("ticket") deviates from the established ToolParams API convention. As per coding guidelines, C# Editor tools should use methods like RequireString() for string parameters.

🔧 Proposed fix
-    var ticketResult = p.GetRequired("ticket");
-    if (!ticketResult.IsSuccess)
-        return new ErrorResponse(ticketResult.ErrorMessage);
-
-    string ticket = ticketResult.Value;
+    var ticketResult = p.RequireString("ticket");
+    if (!ticketResult.IsSuccess)
+        return new ErrorResponse(ticketResult.ErrorMessage);
+
+    string ticket = ticketResult.Value;

As per coding guidelines: "Use ToolParams for consistent parameter validation in C# Editor tools with methods like GetInt(), RequireString()".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/PollJob.cs` around lines 18 - 21, The code is using
p.GetRequired("ticket") instead of the ToolParams guideline method; replace the
call with p.RequireString("ticket") (or the equivalent RequireString method on
ToolParams) and update handling to use the returned result (e.g., check success
and use its Value or return new ErrorResponse(result.ErrorMessage)) so the
Ticket extraction uses ToolParams.RequireString(), referenced variable p, and
the existing ErrorResponse/ticketResult flow.
docs/plans/2026-02-25-queue-summary-ui-plan.md-35-38 (1)

35-38: ⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks.

These fenced blocks are unlabeled and trigger MD040. Please annotate each block with an explicit language (for example bash, json, or text).

Also applies to: 491-494, 767-770, 797-799, 811-813

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-25-queue-summary-ui-plan.md` around lines 35 - 38, The
Markdown fenced code blocks like the one containing "refresh_unity(scope=all,
compile=request, wait_for_ready=true)" and the "read_console(types=[\"error\"]) 
→ 0 errors" output are unlabeled and trigger MD040; edit each such fenced block
(including the other occurrences mentioned) to add an explicit language
identifier (e.g., ```bash, ```text or ```console) after the opening backticks so
the block is annotated and the linter no longer flags MD040.
MCPForUnity/Editor/Helpers/UIAssetSync.cs-71-72 (1)

71-72: ⚠️ Potential issue | 🟡 Minor

Anchor destination path to project root explicitly.

Line 71 builds a full path from a relative base (Assets/...) using current working directory semantics. Use Path.GetDirectoryName(Application.dataPath) as the root so writes are always deterministic to the active project.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Helpers/UIAssetSync.cs` around lines 71 - 72, The code
currently computes destPath with Path.GetFullPath(Path.Combine(SyncedBasePath,
relativePath)) which uses the process CWD; change the construction to anchor
against the Unity project root by using
Path.GetDirectoryName(Application.dataPath) as the base before combining with
SyncedBasePath and relativePath so destPath and destDir are deterministic.
Update the logic that sets destPath and destDir (the variables destPath, destDir
and the expression using SyncedBasePath/relativePath) to first compute
projectRoot = Path.GetDirectoryName(Application.dataPath) and then combine
projectRoot with SyncedBasePath and relativePath to produce the final paths.
docs/plans/2026-02-25-queue-summary-ui-plan.md-15-15 (1)

15-15: ⚠️ Potential issue | 🟡 Minor

Fix heading-level jump in task sections.

Line 15 jumps from H1 to H3. Use H2 for Task 1 (and peers) to satisfy MD001.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-25-queue-summary-ui-plan.md` at line 15, The markdown
heading "Task 1: Expose GetAllJobs on CommandQueue" is currently H3 (###)
causing a jump from the document H1; change this and all peer task headings
("Task 1", "Task 2", etc.) from H3 to H2 (##) so the section hierarchy is H1 ->
H2 -> H3 and MD001 is satisfied; update the lines with the exact heading texts
to use "##" instead of "###".
MCPForUnity/Editor/Tools/TicketStore.cs-39-45 (1)

39-45: ⚠️ Potential issue | 🟡 Minor

CleanExpired should also clean cancelled terminal jobs.

The expiry filter currently excludes JobStatus.Cancelled, so cancelled jobs can accumulate when they are not removed immediately.

Proposed fix
-                .Where(kvp => (kvp.Value.Status == JobStatus.Done || kvp.Value.Status == JobStatus.Failed)
+                .Where(kvp => (kvp.Value.Status == JobStatus.Done
+                               || kvp.Value.Status == JobStatus.Failed
+                               || kvp.Value.Status == JobStatus.Cancelled)
                               && kvp.Value.CompletedAt.HasValue
                               && DateTime.UtcNow - kvp.Value.CompletedAt.Value > expiry)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/TicketStore.cs` around lines 39 - 45, The
CleanExpired logic in TicketStore.CleanExpired currently filters terminal jobs
by JobStatus.Done or JobStatus.Failed and thus misses cancelled terminal jobs;
update the expiry predicate used when building expired (the LINQ Where over
_jobs entries) to also include kvp.Value.Status == JobStatus.Cancelled so
cancelled jobs with CompletedAt older than expiry are selected and removed from
_jobs; ensure the selection still checks CompletedAt.HasValue and the time-based
comparison remains unchanged.
MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.cs-216-221 (1)

216-221: ⚠️ Potential issue | 🟡 Minor

Queued jobs show incorrect progress (1/N) before execution starts.

Line 219 uses job.CurrentIndex + 1 for non-done jobs, so queued jobs render as if one command already ran. Render queued as 0/N.

Proposed fix
-                int idx = job.Status == JobStatus.Done ? job.Commands.Count : job.CurrentIndex + 1;
+                int idx = job.Status switch
+                {
+                    JobStatus.Queued => 0,
+                    JobStatus.Done => job.Commands.Count,
+                    _ => System.Math.Clamp(job.CurrentIndex + 1, 1, job.Commands.Count)
+                };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.cs` around lines
216 - 221, The progress calculation currently uses job.CurrentIndex + 1 for
non-done jobs which makes queued jobs show "1/N"; change the logic in
McpQueueSection (the block that builds progressText) to use job.CurrentIndex for
non-Done jobs and leave Done as job.Commands.Count, i.e. set idx = job.Status ==
JobStatus.Done ? job.Commands.Count : job.CurrentIndex so queued jobs render as
"0/N".
MCPForUnity/Editor/Tools/CommandQueue.cs-144-146 (1)

144-146: ⚠️ Potential issue | 🟡 Minor

Done-job progress is off by one in summary output.

Completed jobs currently report progress from CurrentIndex directly, which yields N-1/N after a normal full run.

Proposed fix
 if (j.Commands != null && j.Commands.Count > 0)
-    entry["p"] = $"{j.CurrentIndex + (j.Status == JobStatus.Done ? 0 : 1)}/{j.Commands.Count}";
+{
+    var completed = j.Status == JobStatus.Done
+        ? j.Commands.Count
+        : Math.Min(j.CurrentIndex + 1, j.Commands.Count);
+    entry["p"] = $"{completed}/{j.Commands.Count}";
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/CommandQueue.cs` around lines 144 - 146, The
progress string in CommandQueue is off by one for completed jobs because it uses
j.CurrentIndex + (j.Status == JobStatus.Done ? 0 : 1); change the assignment
that sets entry["p"] so it always adds 1 to j.CurrentIndex (e.g.
$"{j.CurrentIndex + 1}/{j.Commands.Count}") to ensure a finished job reports
N/N; update the line inside the block that checks j.Commands to use
j.CurrentIndex + 1 unconditionally.
🧹 Nitpick comments (10)
MCPForUnity/Editor/Tools/QueueStatus.cs (1)

14-18: Consider wrapping @params with ToolParams for consistency with other tools.

This tool doesn't require any input parameters, so there's nothing to validate. However, some tools in this directory use ToolParams to wrap the incoming JObject. Adopting the same pattern here — even if just var tp = new ToolParams(@params); — keeps the tool surface consistent and makes it trivial to add optional parameters (e.g., filters) later.

Per coding guidelines for MCPForUnity/Editor/Tools/*.cs: "Use ToolParams for consistent parameter validation in C# Editor tools with methods like GetInt(), RequireString()".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/QueueStatus.cs` around lines 14 - 18, Wrap the
incoming JObject in a ToolParams instance inside HandleCommand to match other
tools' patterns: create a local variable like tp = new ToolParams(`@params`) at
the start of the method and use tp (even if not used further) to preserve
consistency and make future optional parameter handling trivial; update the
HandleCommand method signature/body so it constructs ToolParams before calling
CommandGatewayState.Queue.GetSummary() and returning the SuccessResponse.
MCPForUnity/Editor/Tools/CommandClassifier.cs (2)

48-58: CausesDomainReload returns true when compile param is absent — worth documenting explicitly.

@params.Value<string>("compile") returns null when the key is missing, and null != "none" evaluates to true. This means refresh_unity with no compile key is treated as domain-reload-causing, which is the correct conservative default — but it's a non-obvious null comparison. A short inline comment would prevent future readers from misreading it as a bug.

♻️ Proposed comment
-    "refresh_unity" => `@params.Value`<string>("compile") != "none",
+    // null (omitted) or any value other than "none" triggers a reload
+    "refresh_unity" => `@params.Value`<string>("compile") != "none",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/CommandClassifier.cs` around lines 48 - 58, The
CausesDomainReload method treats a missing "compile" key as causing a domain
reload because `@params.Value`<string>("compile") returns null and null != "none"
is true; add a brief inline comment above the "refresh_unity" case (referencing
CausesDomainReload and the `@params.Value`<string>("compile") expression)
explaining that returning true on missing/unknown compile values is an
intentional conservative default to avoid silently skipping reloads.

33-43: ClassifyBatch on an empty array returns Instant — consider documenting this sentinel.

Initialising max to ExecutionTier.Instant means a zero-command batch resolves to Instant. This is likely intentional (no commands = no restriction), but a brief comment guards against someone assuming a "neutral" default should be Smooth.

♻️ Proposed comment
-    var max = ExecutionTier.Instant;
+    // Start at the lowest tier; an empty batch resolves to Instant (no restriction).
+    var max = ExecutionTier.Instant;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/CommandClassifier.cs` around lines 33 - 43,
ClassifyBatch currently initializes max to ExecutionTier.Instant so an empty
commands array returns Instant; add a short comment above ClassifyBatch
explaining that ExecutionTier.Instant is being used intentionally as the
sentinel/default for "no commands" (and reference that ClassifyBatch calls
Classify for each command), so future readers understand that an empty batch
represents no restrictions rather than a neutral Smooth value.
MCPForUnity/Editor/Tools/CommandGatewayState.cs (1)

37-41: Cache the ProcessTick delegate to avoid a per-frame allocation.

OnUpdate constructs a new async lambda every editor tick (potentially 100 + calls/second), causing continuous heap pressure. The lambda closes over no mutable instance state, so it can be lifted to a static field.

♻️ Proposed fix
+    static readonly Func<string, JObject, Task<object>> _executeCommand =
+        (tool, `@params`) => CommandRegistry.InvokeCommandAsync(tool, `@params`);
+
     static void OnUpdate()
     {
-        Queue.ProcessTick(async (tool, `@params`) =>
-            await CommandRegistry.InvokeCommandAsync(tool, `@params`));
+        Queue.ProcessTick(_executeCommand);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/CommandGatewayState.cs` around lines 37 - 41,
OnUpdate currently creates a new async lambda each tick which allocates; pull
that lambda out into a static readonly delegate and pass the cached delegate to
Queue.ProcessTick instead. Create a static readonly Func<ToolType, ParamType,
Task> (or matching delegate signature used by Queue.ProcessTick) field (name
e.g. s_processTickHandler) that calls CommandRegistry.InvokeCommandAsync(tool,
`@params`) and replace the inline async lambda in OnUpdate with that field to
avoid per-frame allocations; keep the call to CommandRegistry.InvokeCommandAsync
unchanged.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandClassifierTests.cs (1)

27-40: Consider adding coverage for create (manage_scene) and pause/stop (manage_editor).

ClassifyManageScene shares the Heavy arm across "create" or "load" or "save", but create has no dedicated test. Likewise ClassifyManageEditor maps "pause" and "stop" to Heavy with no test coverage. If the switch arms are ever edited, the missing cases won't be caught by the suite.

Also applies to: 59-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandClassifierTests.cs`
around lines 27 - 40, Add unit tests to cover the missing branches by invoking
CommandClassifier.Classify with the same patterns used in existing tests: add a
test Classify_ManageScene_Create_ReturnsHeavy that calls
CommandClassifier.Classify("manage_scene", ExecutionTier.Smooth, new JObject {
["action"] = "create" }) and asserts ExecutionTier.Heavy, and add tests
Classify_ManageEditor_Pause_ReturnsHeavy and
Classify_ManageEditor_Stop_ReturnsHeavy that call
CommandClassifier.Classify("manage_editor", ExecutionTier.Smooth, new JObject {
["action"] = "pause" }) and new JObject { ["action"] = "stop" } respectively and
assert ExecutionTier.Heavy to ensure the "create", "pause", and "stop" switch
arms are covered.
MCPForUnity/Editor/Tools/CommandRegistry.cs (1)

198-207: GetToolTier silently returns Smooth for unregistered commands — consider whether that's always correct.

Callers (e.g., BatchExecute building a command list before CommandRegistry.Initialize() has run) will silently receive Smooth rather than an error. If this sentinel is intentional, a brief XML-doc note that "unregistered or not-yet-initialized tools default to Smooth" would prevent future confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Tools/CommandRegistry.cs` around lines 198 - 207,
GetToolTier currently returns ExecutionTier.Smooth when a command isn't found in
_handlers, which can hide callers' initialization/order issues (e.g.,
BatchExecute calling GetToolTier before CommandRegistry.Initialize); update the
XML doc summary for GetToolTier to explicitly state that unregistered or
not-yet-initialized commands will default to ExecutionTier.Smooth so callers are
aware of the sentinel behavior and can change their flow or call Initialize
first.
MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs (1)

332-349: Add explicit warning when Queue section assets fail to load.

The Queue load block has no else diagnostic, unlike Tools/Resources. A warning here makes empty Queue-tab failures much easier to diagnose.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs` around lines 332 -
349, The Queue section load lacks diagnostic warnings when assets fail to load;
update the block that references queueTree and queueStyleSheet (and the creation
of McpQueueSection) to log explicit warnings when queueTree is null and when
queueStyleSheet is null so failures are visible—use the same logging strategy as
the Tools/Resources block (e.g., call Debug.LogWarning or an existing logger) to
report "McpQueueSection.uxml not found" and "McpQueueSection.uss not found" and
ensure you do not attempt to instantiate McpQueueSection or add a null
stylesheet to rootVisualElement when the assets are missing.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/BatchExecuteAsyncTests.cs (1)

26-52: Strengthen response assertions to avoid false positives.

These tests currently only check Is.Not.Null, so contract regressions can slip through. Please assert expected response kind/status fields (e.g., error for missing/invalid ticket, structured status payload for queue status).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/BatchExecuteAsyncTests.cs`
around lines 26 - 52, Update the tests to assert on the response contract
instead of only non-null: for PollJob.HandleCommand in
PollJob_NullParams_ReturnsError, PollJob_MissingTicket_ReturnsError, and
PollJob_InvalidTicket_ReturnsNotFound assert the returned JObject contains an
error/ kind/status field (e.g., result["kind"] == "error" or result["status"] ==
"error") and for PollJob_InvalidTicket also assert an appropriate not-found
indicator (status code or message). For QueueStatus.HandleCommand in
QueueStatus_ReturnsStatusObject assert the response is a structured status
payload (e.g., contains expected keys like "queue", "length", or "items") and
validate their types (JObject/JArray or numeric) to prevent false positives.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandQueueTests.cs (2)

79-89: Add a null/empty-agent cancellation authorization test.

Given cancellation is ownership-sensitive, add a regression test that Cancel(ticket, null) and Cancel(ticket, "") are rejected.

Proposed additional test
+        [Test]
+        public void Cancel_NullOrEmptyAgent_Fails()
+        {
+            var cmds = new List<BatchCommand>
+            {
+                new() { Tool = "refresh_unity", Params = new JObject(), Tier = ExecutionTier.Heavy }
+            };
+            var job = _queue.Submit("agent-1", "heavy", false, cmds);
+
+            Assert.That(_queue.Cancel(job.Ticket, null), Is.False);
+            Assert.That(_queue.Cancel(job.Ticket, ""), Is.False);
+            Assert.That(job.Status, Is.EqualTo(JobStatus.Queued));
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandQueueTests.cs`
around lines 79 - 89, Update the Cancel_WrongAgent_Fails test to also assert
cancellation is rejected when the caller agent is null or empty: call
_queue.Cancel(job.Ticket, null) and _queue.Cancel(job.Ticket, "") and assert
both return false and that job.Status remains JobStatus.Queued; keep the
existing wrong-agent assertion. Use the same job created in
Cancel_WrongAgent_Fails so you validate authorization logic in _queue.Cancel for
null and empty caller values.

203-204: Tighten “proceeds” assertions to avoid false positives.

Is.Not.EqualTo(JobStatus.Queued) can still pass if a job fails or is cancelled. These tests should assert expected terminal/running states explicitly (and preferably Error is null).

Suggested assertion pattern
- Assert.That(job.Status, Is.Not.EqualTo(JobStatus.Queued));
+ Assert.That(job.Status, Is.EqualTo(JobStatus.Running).Or.EqualTo(JobStatus.Done));
+ Assert.That(job.Error, Is.Null);

Also applies to: 217-218, 237-238

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandQueueTests.cs`
around lines 203 - 204, Replace the loose negative assertion
"Assert.That(job.Status, Is.Not.EqualTo(JobStatus.Queued))" with explicit
positive assertions of the expected state (e.g., Assert.That(job.Status,
Is.EqualTo(JobStatus.Running)) or Assert.That(job.Status,
Is.EqualTo(JobStatus.Completed)) depending on the test) and add an assertion
that the job has no error (e.g., Assert.IsNull(job.Error)). Update all
occurrences in CommandQueueTests.cs where the loose check appears (the instance
using the local variable job at the shown lines and the other two occurrences)
so each test asserts the precise terminal or running state and verifies Error is
null.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs`:
- Around line 106-112: GetMcpPackageRootPath currently returns
UIAssetSync.SyncedBasePath when UIAssetSync.NeedsSync() is true, which changes
the package root semantics and breaks downstream callers like GetPackageJson
that expect the real package root (so package.json can be found); instead,
preserve the true package root return value from GetMcpPackageRootPath and limit
the WSL workaround to UI-specific lookups only: revert the early return in
GetMcpPackageRootPath to always return the actual package root, and update any
code that loads UXML/USS (or GetPackageJson) to check UIAssetSync.NeedsSync()
and use UIAssetSync.SyncedBasePath only for UI asset resolution while keeping
GetPackageJson pointed at the real package root.

In `@MCPForUnity/Editor/Helpers/UIAssetSync.cs`:
- Around line 52-87: The static initializer UIAssetSync.UIAssetSync() currently
performs unguarded file I/O (File.ReadAllText, File.WriteAllText,
Directory.CreateDirectory) which can throw and break type initialization; wrap
the per-file operations inside a try/catch that catches IOException and general
Exception, log the error with Debug.LogError or Debug.LogException including the
relativePath and exception, skip that file and continue so the static ctor never
throws, and ensure the existing anyUpdated/AssetDatabase.Refresh logic remains
intact; keep the early returns from NeedsSync() and GetPackagePhysicalRoot()
unchanged.

In `@MCPForUnity/Editor/Tools/BatchExecute.cs`:
- Around line 255-273: The async batch loop currently skips malformed or
missing-tool entries and doesn't enforce disabled-tool policy; change it to
validate every command token and fail the entire batch if any invalid entry or a
disabled tool is found: for each token parsed in the loop (the block using
NormalizeParameterKeys, CommandRegistry.GetToolTier, CommandClassifier.Classify,
CommandClassifier.CausesDomainReload and creating new BatchCommand) detect
missing/empty toolName or non-JObject params and collect/return an ErrorResponse
listing the invalid entries instead of continuing, and check the
tool-enabled/disabled status the same way the sync path does (use the same
CommandRegistry/CommandClassifier call used in the sync path) and return an
ErrorResponse if any tool is disabled before adding commands to the commands
list.
- Around line 280-304: The current loop in BatchExecute (using
CommandRegistry.InvokeCommandAsync and collecting job.Results) unconditionally
sets job.Status = JobStatus.Done and returns a SuccessResponse even if some
commands produced ErrorResponse; after the foreach, inspect job.Results for any
ErrorResponse (or exceptions collected), and if any exist set job.Status =
JobStatus.Failed, populate job.Error/CompletedAt and return an ErrorResponse
(including ticket and results); only set Done and return SuccessResponse when no
ErrorResponse entries are present. Ensure you reference the existing symbols:
CommandRegistry.InvokeCommandAsync, ErrorResponse, SuccessResponse, job.Results,
JobStatus, atomic, and cmd.Tool when building the error payload.
- Around line 284-286: The blocking call in HandleAsyncSubmit uses
.ConfigureAwait(true).GetAwaiter().GetResult() which can deadlock the Unity
editor; change HandleAsyncSubmit to be async (e.g., Task or Task<TResult>
return) and replace that blocking call with await
CommandRegistry.InvokeCommandAsync(cmd.Tool, cmd.Params) so the call yields to
the Unity synchronization context; update any callers of HandleAsyncSubmit to
await the new async signature (or otherwise handle the returned Task), and keep
job.Results.Add(result) after the awaited call.

In `@MCPForUnity/Editor/Tools/CommandQueue.cs`:
- Around line 117-123: The Remove method currently clears _activeHeavyTicket
directly which bypasses the busy-hold and cooldown logic in ProcessTick; to fix
this, stop clearing _activeHeavyTicket inside Remove (leave
_smoothInFlight.Remove(ticket) and return _store.Remove(ticket) only) and let
ProcessTick handle clearing the active heavy slot and enforcing
cooldown/busy-hold, or if immediate clearing is absolutely required add a guard
that only clears _activeHeavyTicket when not in the editor-busy state/cooldown
(use the same busy/cooldown checks used by ProcessTick) so the
busy-hold/cooldown semantics remain consistent.
- Around line 61-66: In the Cancel method, the current check lets agent==null
bypass owner checks; change the authorization so a null agent cannot cancel
others: after retrieving job via _store.GetJob(ticket) and confirming job.Status
== JobStatus.Queued, require agent to be non-null and equal to job.Agent before
proceeding (i.e., return false if agent is null or agent != job.Agent). Update
the conditional that currently reads "if (job.Agent != agent && agent != null)
return false;" to enforce this stricter check using Cancel, job.Agent, and
JobStatus.
- Around line 199-208: RestoreFromJson currently repopulates _heavyQueue from
_store but doesn't clear transient runtime state first, causing duplicates on
repeated restores; before calling _store.FromJson or before re-enqueuing jobs in
RestoreFromJson, clear/reset the runtime-only fields: call _heavyQueue.Clear()
(or recreate it), clear the _smoothInFlight collection (e.g.,
_smoothInFlight.Clear() or new empty instance), and reset _activeHeavyTicket to
its default/null value, then proceed to call _store.FromJson and re-enqueue
tickets from _store.GetQueuedJobs() as you already do.
- Around line 37-45: Submit currently assigns the caller-owned list to
job.Commands which allows external mutation; instead make a defensive copy in
Submit by creating a new List<BatchCommand> and deep-cloning each element before
assigning to job.Commands (e.g., construct new BatchCommand instances copying
primitive fields and using JToken/JObject.DeepClone() or a BatchCommand.Clone
method for Params), so references to the original list or JObjects cannot modify
the queued job; update Submit in CommandQueue and add/use a Clone method on
BatchCommand if needed.

In `@MCPForUnity/Editor/Tools/GatewayJobLogger.cs`:
- Around line 82-87: The GatewayJobLogger.Log() call performs a synchronous
File.AppendAllText using LogPath on the Unity editor main thread (invoked from
PollJob.HandleCommand), which can block; change Log to perform non-blocking
background writes (e.g., move the file write and directory-creation logic into
Task.Run or an internal background writer/queue) so the main thread only
enqueues the log entry and returns immediately; ensure you reference and use
LogPath, the existing entry.ToString(Formatting.None) payload, and preserve
directory creation semantics (create dir inside the background task or use a
thread-safe check) and handle exceptions from the background task so failures
are logged without throwing on the main thread.

---

Outside diff comments:
In `@MCPForUnity/Editor/Tools/BatchExecute.cs`:
- Around line 35-66: The top-level parameter parsing in HandleCommand is using
JObject.Value/ indexing directly; replace this with a ToolParams instance (e.g.
new ToolParams(`@params`)) and call its helpers (GetInt(), GetBool() / GetString()
/ RequireString() as appropriate) to read "async", "failFast", "parallel",
"maxParallelism" and the "commands" array so validation is consistent with other
editor tools; specifically, change the commandsToken extraction to use
ToolParams to require/validate the "commands" entry and replace usages of
`@params.Value`<int?>("maxParallelism") and `@params.Value`<bool?>("async") etc.
with the matching ToolParams methods while preserving the async vs legacy flow
inside HandleCommand and when calling HandleAsyncSubmit.

---

Minor comments:
In `@docs/plans/2026-02-25-gateway-async-awareness-design.md`:
- Around line 27-31: The fenced code block containing the conditional snippet
with symbols job.CausesDomainReload, TestJobManager.HasRunningJob, and
EditorApplication.isCompiling is missing a language specifier; update the
opening fence to include a language token (for example "text") so the block
becomes ```text ... ``` to satisfy markdownlint while preserving the exact
contents of the snippet.

In `@docs/plans/2026-02-25-gateway-async-awareness-plan.md`:
- Around line 90-94: The markdown contains unlabeled fenced code blocks (e.g.,
the run_tests(...) snippet and other blocks referenced at lines 90, 121, 886,
893, 904) causing MD040; update each triple-backtick fence to include an
appropriate language identifier (for the run_tests snippet use text or bash, and
for other blocks choose csharp/json/text as appropriate) so every code fence is
labeled (e.g., ```text or ```csharp) while keeping the block contents unchanged.
- Line 13: Heading level jumps from H1 to H3 for the section titled "Task 1: Add
`CausesDomainReload` to CommandClassifier"; change the heading token from "###
Task 1: Add `CausesDomainReload` to CommandClassifier" to "## Task 1: Add
`CausesDomainReload` to CommandClassifier" so the document uses a proper H2 and
satisfies markdown lint MD001.

In `@docs/plans/2026-02-25-queue-summary-ui-design.md`:
- Line 13: Add explicit language specifiers to the fenced code blocks containing
ASCII-art/pseudo-code (the two triple-backtick blocks flagged by MD040) by
changing their opening fences from ``` to a tagged form like ```text (or
```ascii) so the linter recognizes them; update both code fences so each opening
triple-backtick includes the chosen language tag.

In `@docs/plans/2026-02-25-queue-summary-ui-plan.md`:
- Around line 35-38: The Markdown fenced code blocks like the one containing
"refresh_unity(scope=all, compile=request, wait_for_ready=true)" and the
"read_console(types=[\"error\"])  → 0 errors" output are unlabeled and trigger
MD040; edit each such fenced block (including the other occurrences mentioned)
to add an explicit language identifier (e.g., ```bash, ```text or ```console)
after the opening backticks so the block is annotated and the linter no longer
flags MD040.
- Line 15: The markdown heading "Task 1: Expose GetAllJobs on CommandQueue" is
currently H3 (###) causing a jump from the document H1; change this and all peer
task headings ("Task 1", "Task 2", etc.) from H3 to H2 (##) so the section
hierarchy is H1 -> H2 -> H3 and MD001 is satisfied; update the lines with the
exact heading texts to use "##" instead of "###".

In `@MCPForUnity/Editor/Helpers/UIAssetSync.cs`:
- Around line 71-72: The code currently computes destPath with
Path.GetFullPath(Path.Combine(SyncedBasePath, relativePath)) which uses the
process CWD; change the construction to anchor against the Unity project root by
using Path.GetDirectoryName(Application.dataPath) as the base before combining
with SyncedBasePath and relativePath so destPath and destDir are deterministic.
Update the logic that sets destPath and destDir (the variables destPath, destDir
and the expression using SyncedBasePath/relativePath) to first compute
projectRoot = Path.GetDirectoryName(Application.dataPath) and then combine
projectRoot with SyncedBasePath and relativePath to produce the final paths.

In `@MCPForUnity/Editor/Tools/CommandQueue.cs`:
- Around line 144-146: The progress string in CommandQueue is off by one for
completed jobs because it uses j.CurrentIndex + (j.Status == JobStatus.Done ? 0
: 1); change the assignment that sets entry["p"] so it always adds 1 to
j.CurrentIndex (e.g. $"{j.CurrentIndex + 1}/{j.Commands.Count}") to ensure a
finished job reports N/N; update the line inside the block that checks
j.Commands to use j.CurrentIndex + 1 unconditionally.

In `@MCPForUnity/Editor/Tools/PollJob.cs`:
- Around line 57-68: Guard against job.Commands being null in the
JobStatus.Running branch: before using job.Commands.Count or constructing the
progress string, check for null (or use a safe fallback) and compute safeCount =
job.Commands?.Count ?? 0 and safeIndex = Math.Min(job.CurrentIndex + 1,
safeCount); then build the PendingResponse using safeCount and safeIndex for the
message and progress fields (update the Running case that creates the
PendingResponse to reference these safe values rather than job.Commands.Count
directly).
- Around line 18-21: The code is using p.GetRequired("ticket") instead of the
ToolParams guideline method; replace the call with p.RequireString("ticket") (or
the equivalent RequireString method on ToolParams) and update handling to use
the returned result (e.g., check success and use its Value or return new
ErrorResponse(result.ErrorMessage)) so the Ticket extraction uses
ToolParams.RequireString(), referenced variable p, and the existing
ErrorResponse/ticketResult flow.

In `@MCPForUnity/Editor/Tools/TicketStore.cs`:
- Around line 39-45: The CleanExpired logic in TicketStore.CleanExpired
currently filters terminal jobs by JobStatus.Done or JobStatus.Failed and thus
misses cancelled terminal jobs; update the expiry predicate used when building
expired (the LINQ Where over _jobs entries) to also include kvp.Value.Status ==
JobStatus.Cancelled so cancelled jobs with CompletedAt older than expiry are
selected and removed from _jobs; ensure the selection still checks
CompletedAt.HasValue and the time-based comparison remains unchanged.

In `@MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.cs`:
- Around line 216-221: The progress calculation currently uses job.CurrentIndex
+ 1 for non-done jobs which makes queued jobs show "1/N"; change the logic in
McpQueueSection (the block that builds progressText) to use job.CurrentIndex for
non-Done jobs and leave Done as job.Commands.Count, i.e. set idx = job.Status ==
JobStatus.Done ? job.Commands.Count : job.CurrentIndex so queued jobs render as
"0/N".

In `@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/TicketStoreTests.cs`:
- Around line 113-121: The test GetQueuedJobs_OrderedByCreationTime is flaky
because CreateJob may assign identical CreatedAt timestamps; update the test to
avoid relying on millisecond ordering by either explicitly setting distinct
CreatedAt values on j1 and j2 after creation (e.g., j1.CreatedAt = now;
j2.CreatedAt = now.AddMilliseconds(1)) before calling _store.GetQueuedJobs(), or
change the assertions to check that both tickets are present without requiring
specific positions (e.g., Assert.That(queued.Select(q => q.Ticket),
Is.EquivalentTo(new[] { j1.Ticket, j2.Ticket }))); modify only the test logic
around CreateJob, j1/j2, and the assertions so GetQueuedJobs and _store remain
unchanged.

---

Nitpick comments:
In `@MCPForUnity/Editor/Tools/CommandClassifier.cs`:
- Around line 48-58: The CausesDomainReload method treats a missing "compile"
key as causing a domain reload because `@params.Value`<string>("compile") returns
null and null != "none" is true; add a brief inline comment above the
"refresh_unity" case (referencing CausesDomainReload and the
`@params.Value`<string>("compile") expression) explaining that returning true on
missing/unknown compile values is an intentional conservative default to avoid
silently skipping reloads.
- Around line 33-43: ClassifyBatch currently initializes max to
ExecutionTier.Instant so an empty commands array returns Instant; add a short
comment above ClassifyBatch explaining that ExecutionTier.Instant is being used
intentionally as the sentinel/default for "no commands" (and reference that
ClassifyBatch calls Classify for each command), so future readers understand
that an empty batch represents no restrictions rather than a neutral Smooth
value.

In `@MCPForUnity/Editor/Tools/CommandGatewayState.cs`:
- Around line 37-41: OnUpdate currently creates a new async lambda each tick
which allocates; pull that lambda out into a static readonly delegate and pass
the cached delegate to Queue.ProcessTick instead. Create a static readonly
Func<ToolType, ParamType, Task> (or matching delegate signature used by
Queue.ProcessTick) field (name e.g. s_processTickHandler) that calls
CommandRegistry.InvokeCommandAsync(tool, `@params`) and replace the inline async
lambda in OnUpdate with that field to avoid per-frame allocations; keep the call
to CommandRegistry.InvokeCommandAsync unchanged.

In `@MCPForUnity/Editor/Tools/CommandRegistry.cs`:
- Around line 198-207: GetToolTier currently returns ExecutionTier.Smooth when a
command isn't found in _handlers, which can hide callers' initialization/order
issues (e.g., BatchExecute calling GetToolTier before
CommandRegistry.Initialize); update the XML doc summary for GetToolTier to
explicitly state that unregistered or not-yet-initialized commands will default
to ExecutionTier.Smooth so callers are aware of the sentinel behavior and can
change their flow or call Initialize first.

In `@MCPForUnity/Editor/Tools/QueueStatus.cs`:
- Around line 14-18: Wrap the incoming JObject in a ToolParams instance inside
HandleCommand to match other tools' patterns: create a local variable like tp =
new ToolParams(`@params`) at the start of the method and use tp (even if not used
further) to preserve consistency and make future optional parameter handling
trivial; update the HandleCommand method signature/body so it constructs
ToolParams before calling CommandGatewayState.Queue.GetSummary() and returning
the SuccessResponse.

In `@MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs`:
- Around line 332-349: The Queue section load lacks diagnostic warnings when
assets fail to load; update the block that references queueTree and
queueStyleSheet (and the creation of McpQueueSection) to log explicit warnings
when queueTree is null and when queueStyleSheet is null so failures are
visible—use the same logging strategy as the Tools/Resources block (e.g., call
Debug.LogWarning or an existing logger) to report "McpQueueSection.uxml not
found" and "McpQueueSection.uss not found" and ensure you do not attempt to
instantiate McpQueueSection or add a null stylesheet to rootVisualElement when
the assets are missing.

In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/BatchExecuteAsyncTests.cs`:
- Around line 26-52: Update the tests to assert on the response contract instead
of only non-null: for PollJob.HandleCommand in PollJob_NullParams_ReturnsError,
PollJob_MissingTicket_ReturnsError, and PollJob_InvalidTicket_ReturnsNotFound
assert the returned JObject contains an error/ kind/status field (e.g.,
result["kind"] == "error" or result["status"] == "error") and for
PollJob_InvalidTicket also assert an appropriate not-found indicator (status
code or message). For QueueStatus.HandleCommand in
QueueStatus_ReturnsStatusObject assert the response is a structured status
payload (e.g., contains expected keys like "queue", "length", or "items") and
validate their types (JObject/JArray or numeric) to prevent false positives.

In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandClassifierTests.cs`:
- Around line 27-40: Add unit tests to cover the missing branches by invoking
CommandClassifier.Classify with the same patterns used in existing tests: add a
test Classify_ManageScene_Create_ReturnsHeavy that calls
CommandClassifier.Classify("manage_scene", ExecutionTier.Smooth, new JObject {
["action"] = "create" }) and asserts ExecutionTier.Heavy, and add tests
Classify_ManageEditor_Pause_ReturnsHeavy and
Classify_ManageEditor_Stop_ReturnsHeavy that call
CommandClassifier.Classify("manage_editor", ExecutionTier.Smooth, new JObject {
["action"] = "pause" }) and new JObject { ["action"] = "stop" } respectively and
assert ExecutionTier.Heavy to ensure the "create", "pause", and "stop" switch
arms are covered.

In `@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandQueueTests.cs`:
- Around line 79-89: Update the Cancel_WrongAgent_Fails test to also assert
cancellation is rejected when the caller agent is null or empty: call
_queue.Cancel(job.Ticket, null) and _queue.Cancel(job.Ticket, "") and assert
both return false and that job.Status remains JobStatus.Queued; keep the
existing wrong-agent assertion. Use the same job created in
Cancel_WrongAgent_Fails so you validate authorization logic in _queue.Cancel for
null and empty caller values.
- Around line 203-204: Replace the loose negative assertion
"Assert.That(job.Status, Is.Not.EqualTo(JobStatus.Queued))" with explicit
positive assertions of the expected state (e.g., Assert.That(job.Status,
Is.EqualTo(JobStatus.Running)) or Assert.That(job.Status,
Is.EqualTo(JobStatus.Completed)) depending on the test) and add an assertion
that the job has no error (e.g., Assert.IsNull(job.Error)). Update all
occurrences in CommandQueueTests.cs where the loose check appears (the instance
using the local variable job at the shown lines and the other two occurrences)
so each test asserts the precise terminal or running state and verifies Error is
null.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca3f7bd and 3c70b86.

📒 Files selected for processing (50)
  • MCPForUnity/Editor/Constants/EditorPrefKeys.cs
  • MCPForUnity/Editor/Helpers/AssetPathUtility.cs
  • MCPForUnity/Editor/Helpers/UIAssetSync.cs
  • MCPForUnity/Editor/Helpers/UIAssetSync.cs.meta
  • MCPForUnity/Editor/Tools/BatchExecute.cs
  • MCPForUnity/Editor/Tools/BatchJob.cs
  • MCPForUnity/Editor/Tools/BatchJob.cs.meta
  • MCPForUnity/Editor/Tools/CommandClassifier.cs
  • MCPForUnity/Editor/Tools/CommandClassifier.cs.meta
  • MCPForUnity/Editor/Tools/CommandGatewayState.cs
  • MCPForUnity/Editor/Tools/CommandGatewayState.cs.meta
  • MCPForUnity/Editor/Tools/CommandQueue.cs
  • MCPForUnity/Editor/Tools/CommandQueue.cs.meta
  • MCPForUnity/Editor/Tools/CommandRegistry.cs
  • MCPForUnity/Editor/Tools/ExecutionTier.cs
  • MCPForUnity/Editor/Tools/ExecutionTier.cs.meta
  • MCPForUnity/Editor/Tools/FindGameObjects.cs
  • MCPForUnity/Editor/Tools/GatewayJobLogger.cs
  • MCPForUnity/Editor/Tools/GatewayJobLogger.cs.meta
  • MCPForUnity/Editor/Tools/GetTestJob.cs
  • MCPForUnity/Editor/Tools/ManageScript.cs
  • MCPForUnity/Editor/Tools/ManageShader.cs
  • MCPForUnity/Editor/Tools/McpForUnityToolAttribute.cs
  • MCPForUnity/Editor/Tools/PollJob.cs
  • MCPForUnity/Editor/Tools/PollJob.cs.meta
  • MCPForUnity/Editor/Tools/QueueStatus.cs
  • MCPForUnity/Editor/Tools/QueueStatus.cs.meta
  • MCPForUnity/Editor/Tools/ReadConsole.cs
  • MCPForUnity/Editor/Tools/RefreshUnity.cs
  • MCPForUnity/Editor/Tools/RunTests.cs
  • MCPForUnity/Editor/Tools/TicketStore.cs
  • MCPForUnity/Editor/Tools/TicketStore.cs.meta
  • MCPForUnity/Editor/Windows/Components/Queue.meta
  • MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.cs
  • MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.cs.meta
  • MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.uss
  • MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.uss.meta
  • MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.uxml
  • MCPForUnity/Editor/Windows/Components/Queue/McpQueueSection.uxml.meta
  • MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
  • MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.uxml
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/BatchExecuteAsyncTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandClassifierTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandQueueTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecutionTierTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/TicketStoreTests.cs
  • docs/plans/2026-02-25-gateway-async-awareness-design.md
  • docs/plans/2026-02-25-gateway-async-awareness-plan.md
  • docs/plans/2026-02-25-queue-summary-ui-design.md
  • docs/plans/2026-02-25-queue-summary-ui-plan.md

@Lint111
Copy link
Author

Lint111 commented Feb 25, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 50 out of 50 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Lint111
Copy link
Author

Lint111 commented Feb 25, 2026

@copilot open a new pull request to apply changes based on the comments in in the thread in general

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
docs/plans/2026-02-25-queue-summary-ui-design.md (2)

53-60: Add a language specifier to the fenced code block.

The data-flow block has no language tag, triggering MD040. Use text or plaintext.

✏️ Proposed fix
-```
+```text
 CommandGatewayState.Queue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-25-queue-summary-ui-design.md` around lines 53 - 60, The
fenced code block containing the data-flow diagram (starting with
CommandGatewayState.Queue and lines referencing TicketStore.GetAllJobs,
Queue.HasActiveHeavy, Queue.QueueDepth, Queue.SmoothInFlight,
Queue.IsEditorBusy) is missing a language specifier, which triggers MD040; fix
it by adding a language tag (e.g., text or plaintext) to the opening fence so
the block reads ```text followed by the existing lines and the closing ``` to
satisfy the linter.

13-30: Add a language specifier to the fenced code block.

The ASCII art layout block has no language tag, triggering MD040. Use text or plaintext to silence the warning.

✏️ Proposed fix
-```
+```text
 ┌───────────────────────────────────────────────┐
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-25-queue-summary-ui-design.md` around lines 13 - 30, The
fenced ASCII-art code block is missing a language specifier, triggering MD040;
update the opening triple-backticks for the ASCII diagram (the block starting
with the box diagram/Status Bar and Job List) to include a language tag like
```text or ```plaintext so the linter accepts it and the diagram remains
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/plans/2026-02-25-queue-summary-ui-design.md`:
- Line 19: The ASCII layout at the given diagram is missing the "Failed" counter
declared in the spec (line 36); update the diagram to include all four counters
— "Queued", "Running", "Done", and "Failed" — e.g., change the line showing
"Queued: 2 │ Running: 1 │ Done: 5" to include "│ Failed: 0" (or the appropriate
example value) and ensure separators/spacing match the existing style, or if the
design intentionally omits failures, instead update the component spec to remove
"Failed" so both the spec and the ASCII layout are consistent.

---

Nitpick comments:
In `@docs/plans/2026-02-25-queue-summary-ui-design.md`:
- Around line 53-60: The fenced code block containing the data-flow diagram
(starting with CommandGatewayState.Queue and lines referencing
TicketStore.GetAllJobs, Queue.HasActiveHeavy, Queue.QueueDepth,
Queue.SmoothInFlight, Queue.IsEditorBusy) is missing a language specifier, which
triggers MD040; fix it by adding a language tag (e.g., text or plaintext) to the
opening fence so the block reads ```text followed by the existing lines and the
closing ``` to satisfy the linter.
- Around line 13-30: The fenced ASCII-art code block is missing a language
specifier, triggering MD040; update the opening triple-backticks for the ASCII
diagram (the block starting with the box diagram/Status Bar and Job List) to
include a language tag like ```text or ```plaintext so the linter accepts it and
the diagram remains unchanged.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c70b86 and ff87441.

📒 Files selected for processing (1)
  • docs/plans/2026-02-25-queue-summary-ui-design.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants