🤖 fix: show plan auto-routing decision status in sidebar#2536
🤖 fix: show plan auto-routing decision status in sidebar#2536
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review Addressed: pulsing state now applies only to the transient auto-routing decision status. |
|
@codex review Resolved prior thread and pushed a fix that limits pulsing to the transient auto-routing status. |
There was a problem hiding this comment.
💡 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".
|
@codex review Addressed stale URL carryover by explicitly clearing URL for the transient auto-routing status. |
|
Codex Review: Didn't find any major issues. Nice work! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
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
execororchestrator.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
WorkspaceService.updateAgentStatusfor task handoff orchestration code.TaskService.handleSuccessfulProposePlanAutoHandoff, wrapped auto-routing with:updateAgentStatus(..., { emoji: "🤔", message: "Deciding execution strategy…" })finally { updateAgentStatus(..., null) }WorkspaceStatusDotto pulse only for the transient auto-routing status (not all persisted statuses).🤔icon mapping toEmojiIcon.taskServicetests/mocks to cover the new status lifecycle during auto routing.ProposePlanAutoRoutingDecisionGapstory underApp/Chatfor 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
execororchestrator, 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
src/node/services/taskService.ts:2394resolvePlanAutoHandoffTargetAgentId()is called — up to 15s LLM call with no IPC emissionssrc/node/services/planExecutorRouter.tsroutePlanToExecutor()usesgenerateText()with 15s timeout (PLAN_EXECUTOR_ROUTING_TIMEOUT_MS)src/node/services/workspaceService.ts:1165updateAgentStatus()is private — persists to disk + emitsactivityeventsrc/browser/components/WorkspaceStatusIndicator.tsx:54nullwhen!canInterrupt && !isStarting(the gap) — renders nothingsrc/browser/components/WorkspaceStatusDot.tsx:23isWorkingisfalseduring gap — dot stops pulsingsrc/common/orpc/schemas/workspace.ts:110WorkspaceActivitySnapshotSchemahasagentStatusbut no handoff phase fieldApproach: Use existing
agentStatusmechanismThe lightest, most consistent approach is to set
agentStatusfromTaskServicebefore the routing LLM call and clear it after. This reuses the existingupdateAgentStatuspipeline (persist → emitactivity→ frontend picks it up) with zero schema changes or new frontend plumbing.The existing rendering path already handles
agentStatuswith emoji + message in the sidebar, and makesWorkspaceStatusDotpulse viaagentStatuspresence (we just need a one-line tweak for the dot).Alternatives considered
New
handoffPhasefield onWorkspaceActivitySnapshotSchema— Semantically cleaner (dedicated typed enum), but requires schema change, newExtensionMetadataServicemethod, new frontend plumbing throughWorkspaceStore→WorkspaceSidebarState→ UI. ~80 LoC for the same visual result.Fake
streaming: true— Pulsing dot works, but lies to the frontend; could confuse interrupt logic and debugging.Synthetic chat message (like ReasoningMessage) — Most visible, but complex lifecycle management for a transient indicator that appears in the chat history.
The
agentStatusapproach 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
updateAgentStatusaccessible toTaskService(~3 LoC)File:
src/node/services/workspaceService.tsChange
updateAgentStatusfromprivatetopublic(it's already safe — queued writes, error handling):Step 2 — Emit routing status before/after the LLM call (~10 LoC)
File:
src/node/services/taskService.ts, inhandleSuccessfulProposePlanAutoHandoffInsert
agentStatusupdates bracketing the routing call (around line 2394):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
agentStatuswas set by the plan agent's laststatus_set, it would be stale during the handoff anyway.Step 3 — Make
WorkspaceStatusDotpulse duringagentStatus(~1 LoC)File:
src/browser/components/WorkspaceStatusDot.tsxCurrently, the dot's
isWorkingonly considerscanInterrupt || isStarting. TheagentStatuspresence means the system is busy, so include it:This makes the sidebar dot pulse blue during routing, matching the streaming/starting visual.
Step 4 — Map the
🤔emoji inEmojiIcon(if needed)File:
src/browser/components/icons/EmojiIcon.tsxCheck if
🤔is already mapped to an SVG icon. Per AGENTS.md: "If a new emoji appears in tool output, extendEmojiIconto map it to an SVG icon."If not mapped, add a mapping to a suitable
lucide-reacticon (e.g.,BrainCircuitorRoute):Visual Result
claude-sonnet-4-20250514 - streaming...claude-sonnet-4-20250514 - starting...claude-sonnet-4-20250514 - streaming...Net LoC Estimate
~15 lines of product code (no test changes needed — existing
agentStatuspipeline is already tested).Validation
make typecheck— confirm no type errors from visibility changemake lint— no new warningsmake test— existing tests pass (no behavioral change toupdateAgentStatus)Generated with
mux• Model:openai:gpt-5.3-codex• Thinking:xhigh• Cost:$2.61