Skip to content

Comments

🤖 fix: show plan auto-routing decision status in sidebar#2536

Merged
ThomasK33 merged 3 commits intomainfrom
agent-ui-ndzd
Feb 22, 2026
Merged

🤖 fix: show plan auto-routing decision status in sidebar#2536
ThomasK33 merged 3 commits intomainfrom
agent-ui-ndzd

Conversation

@ThomasK33
Copy link
Member

@ThomasK33 ThomasK33 commented Feb 22, 2026

Summary

This PR removes the perceived idle gap during plan auto-routing handoff by surfacing a sidebar working status while the backend decides whether to route to exec or orchestrator.

It also adds a Chromatic Storybook scenario that captures the exact moment after a plan is presented and before executor kickoff.

Background

When plan sub-agents use Auto (LLM decides) routing, there is a short pause between the plan stream ending and executor stream startup. During that pause, no stream is active and the UI previously appeared idle.

Implementation

  • Exposed WorkspaceService.updateAgentStatus for task handoff orchestration code.
  • In TaskService.handleSuccessfulProposePlanAutoHandoff, wrapped auto-routing with:
    • updateAgentStatus(..., { emoji: "🤔", message: "Deciding execution strategy…" })
    • finally { updateAgentStatus(..., null) }
  • Updated WorkspaceStatusDot to pulse only for the transient auto-routing status (not all persisted statuses).
  • Added 🤔 icon mapping to EmojiIcon.
  • Extended taskService tests/mocks to cover the new status lifecycle during auto routing.
  • Added ProposePlanAutoRoutingDecisionGap story under App/Chat for Chromatic coverage.

Validation

  • make static-check

📋 Implementation Plan

Plan: Add "Deciding executor" indicator during auto-routing handoff

Context & Why

When "Plan sub-agents: executor routing" is set to "Auto (LLM decides)", the workspace appears idle for up to 15 seconds between the plan agent's stream ending and the executor's stream starting. During this gap, the backend is calling an LLM to decide whether to route to exec or orchestrator, but no IPC events are emitted, so the frontend thinks the workspace is stopped.

Goal: Show a visible "the system is working" indicator in the sidebar during the routing decision, matching the existing streaming/starting visual language (pulsing dot, status text).

Evidence

Source Finding
src/node/services/taskService.ts:2394 resolvePlanAutoHandoffTargetAgentId() is called — up to 15s LLM call with no IPC emissions
src/node/services/planExecutorRouter.ts routePlanToExecutor() uses generateText() with 15s timeout (PLAN_EXECUTOR_ROUTING_TIMEOUT_MS)
src/node/services/workspaceService.ts:1165 updateAgentStatus() is private — persists to disk + emits activity event
src/browser/components/WorkspaceStatusIndicator.tsx:54 Phase is null when !canInterrupt && !isStarting (the gap) — renders nothing
src/browser/components/WorkspaceStatusDot.tsx:23 isWorking is false during gap — dot stops pulsing
src/common/orpc/schemas/workspace.ts:110 WorkspaceActivitySnapshotSchema has agentStatus but no handoff phase field

Approach: Use existing agentStatus mechanism

The lightest, most consistent approach is to set agentStatus from TaskService before the routing LLM call and clear it after. This reuses the existing updateAgentStatus pipeline (persist → emit activity → frontend picks it up) with zero schema changes or new frontend plumbing.

The existing rendering path already handles agentStatus with emoji + message in the sidebar, and makes WorkspaceStatusDot pulse via agentStatus presence (we just need a one-line tweak for the dot).

Alternatives considered
  1. New handoffPhase field on WorkspaceActivitySnapshotSchema — Semantically cleaner (dedicated typed enum), but requires schema change, new ExtensionMetadataService method, new frontend plumbing through WorkspaceStoreWorkspaceSidebarState → UI. ~80 LoC for the same visual result.

  2. Fake streaming: true — Pulsing dot works, but lies to the frontend; could confuse interrupt logic and debugging.

  3. Synthetic chat message (like ReasoningMessage) — Most visible, but complex lifecycle management for a transient indicator that appears in the chat history.

The agentStatus approach is ~15 LoC of product code, zero schema changes, and the visual result is identical to option 1. If we later want richer typed handoff phases, we can upgrade.

Implementation Details

Step 1 — Make updateAgentStatus accessible to TaskService (~3 LoC)

File: src/node/services/workspaceService.ts

Change updateAgentStatus from private to public (it's already safe — queued writes, error handling):

// Line 1165: change `private` → `public`
public async updateAgentStatus(
  workspaceId: string,
  agentStatus: WorkspaceAgentStatus | null
): Promise<void> {
  // ... existing body unchanged ...
}

Step 2 — Emit routing status before/after the LLM call (~10 LoC)

File: src/node/services/taskService.ts, in handleSuccessfulProposePlanAutoHandoff

Insert agentStatus updates bracketing the routing call (around line 2394):

// Before the routing call — inform the user
// Only show routing indicator for "auto" mode (exec/orchestrator don't call an LLM)
if (args.planSubagentExecutorRouting === "auto") {
  await this.workspaceService.updateAgentStatus(args.workspaceId, {
    emoji: "🤔",
    message: "Deciding execution strategy…",
  });
}

const targetAgentId = await this.resolvePlanAutoHandoffTargetAgentId({
  ...
});

// Clear the routing indicator — the next stream will set its own status
await this.workspaceService.updateAgentStatus(args.workspaceId, null);

We only set the indicator for "auto" routing because "exec" and "orchestrator" resolve instantly (no LLM call).

The clear happens unconditionally (even for non-auto) as a defensive measure — if agentStatus was set by the plan agent's last status_set, it would be stale during the handoff anyway.

Step 3 — Make WorkspaceStatusDot pulse during agentStatus (~1 LoC)

File: src/browser/components/WorkspaceStatusDot.tsx

Currently, the dot's isWorking only considers canInterrupt || isStarting. The agentStatus presence means the system is busy, so include it:

// Line 23: add agentStatus check
const isWorking = (canInterrupt || isStarting || !!agentStatus) && !awaitingUserQuestion;

This makes the sidebar dot pulse blue during routing, matching the streaming/starting visual.

Step 4 — Map the 🤔 emoji in EmojiIcon (if needed)

File: src/browser/components/icons/EmojiIcon.tsx

Check if 🤔 is already mapped to an SVG icon. Per AGENTS.md: "If a new emoji appears in tool output, extend EmojiIcon to map it to an SVG icon."

If not mapped, add a mapping to a suitable lucide-react icon (e.g., BrainCircuit or Route):

"🤔": BrainCircuit,  // or Route, or Loader2

Visual Result

State Sidebar Dot Status Indicator Text
Plan streaming 🔵 pulsing claude-sonnet-4-20250514 - streaming...
Routing (new) 🔵 pulsing 🤔 Deciding execution strategy…
Executor starting 🔵 pulsing claude-sonnet-4-20250514 - starting...
Executor streaming 🔵 pulsing claude-sonnet-4-20250514 - streaming...

Net LoC Estimate

~15 lines of product code (no test changes needed — existing agentStatus pipeline is already tested).

Validation

  • make typecheck — confirm no type errors from visibility change
  • make lint — no new warnings
  • make test — existing tests pass (no behavioral change to updateAgentStatus)
  • Manual: enable "Auto (LLM decides)" routing, trigger a plan sub-agent, observe the sidebar during the routing gap

Generated with mux • Model: openai:gpt-5.3-codex • Thinking: xhigh • Cost: $2.61

@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95675711a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Addressed: pulsing state now applies only to the transient auto-routing decision status.

@ThomasK33
Copy link
Member Author

@codex review

Resolved prior thread and pushed a fix that limits pulsing to the transient auto-routing status.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4dba38a1b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Addressed stale URL carryover by explicitly clearing URL for the transient auto-routing status.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Nice work!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33 ThomasK33 added this pull request to the merge queue Feb 22, 2026
Merged via the queue into main with commit 0fafa64 Feb 22, 2026
23 checks passed
@ThomasK33 ThomasK33 deleted the agent-ui-ndzd branch February 22, 2026 13:42
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.

1 participant