cmux NEXT: sidebar rows show each agent session's live lane status (Debug toggle)
A sidebar row only shows an agent session's status when a turn state reaches it through AgentTurnStateStore. acpmux already decides each session's lane (working, waiting on you, failed, lost, idle) and AgentLaneStore holds it from _acpmux/watch, but rows never read it, so a session that failed or waits on you could look idle in the sidebar.
With the Debug toggle Rows show acpmux lane status (sidebar.laneStatus, off by default) on, each row's status mark also follows the lanes of the agent chats in that workspace on this Mac. Rows reuse the existing status glyphs; nothing new is drawn or animated. Off, rows are unchanged and no lane is read.
Precedence
Lane state maps to the row's indicator as working → working, input → waiting, failed/lost → error, idle → nothing. The row shows the strongest of its own activity and its lanes' states, by StatusStack's state rank:
- failed (from the row or any lane)
- waiting on you (from the row or any lane)
- working (a lane's working also sets the row's working slot,
agentWorking) - the row's own done
- an idle lane adds nothing
The row's own state wins a tie and keeps its style. When a lane wins, the row's "since" comes from the lane.
How
SidebarLaneStatus.applyingwraps the row source inSidebarBridge, the same waySidebarAgentLinksdoes in #20905. It reads the toggle and the lanes inside the row's mapping, so a lane change (or flipping the toggle) maps that one row again throughSidebarRowFeed's observation.SidebarLocalLanesis the shared lookup of a workspace's lanes on this Mac (ref.host == turns.localHost,lanes.lanes[session],lane.peer == nil), the same ruleSidebarAgentLinksuses. Every harness's chats are acpmux sessions, so no brand decides anything.- Merge with #20905: both PRs change the
rows:argument on the same line inSidebarBridge. The resolution isrows: SidebarLaneStatus.applying(SidebarAgentLinks.linking(feed.rowSource)).SidebarAgentLinks.linkcan then useSidebarLocalLanes.lanes.
Evidence
SidebarLaneStatusTests maps four workspaces whose chats (claude, codex, opencode, gemini) have lanes in working, input, failed and idle, with nothing in the turn store, through SidebarMapping + SidebarRows. It checks each row's activity, that rows are unchanged with the toggle off, and that a lane change through SidebarRowFeed maps that row again.
Fleet runs of scripts/ci/run-swift-testing-suites.sh Packages/macOS/CmuxNext (swift test --filter ^CmuxNextAppTests\.SidebarLaneStatusTests/):
- Red, 46617840cbeca83067120a318888c1f8a69d8c96 (tests with the pass-through
SidebarLaneStatus):✘ Test eachRowShowsItsLanesState() recorded an issue at SidebarLaneStatusTests.swift:74:13: Expectation failed: (shown["working"] → .idle) == (.working → .working(progress: nil)) ✘ Test aLaneChangeMapsItsRowAgain() recorded an issue at SidebarLaneStatusTests.swift:104:13: Expectation failed: !((pending.work → []).isEmpty → true → true) ✘ Suite SidebarLaneStatusTests failed after 0.008 seconds with 6 issues. - Green, 61b50fbe5bd9af2f24f54138387f211ca39a7ec5:
✔ Test eachRowShowsItsLanesState() passed after 0.007 seconds. ✔ Test toggleOffLeavesRowsAsTheyWere() passed after 0.001 seconds. ✔ Test aLaneChangeMapsItsRowAgain() passed after 0.001 seconds. ✔ Suite SidebarLaneStatusTests passed after 0.010 seconds.
The first red commit (ff9fd14ff9) didn't compile (SidebarSection is ambiguous in the app tests); 4661784 fixes the test and is the behavioral red above. No live app run; this is a Debug toggle that's off by default.
Changelog
- none (Debug-only toggle, off by default)
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Sidebar rows now show each agent session's live acpmux lane status behind a new Debug toggle (sidebar.laneStatus, off by default), so a chat that failed or is waiting on you no longer looks idle in the sidebar.
With the toggle on, a row's status mark follows the acpmux lanes of its agent chats on this Mac, reusing existing status glyphs; lane states map to working, waiting, or error, and idle adds nothing. Precedence follows StatusStack rank (failed, then waiting on you, then working, then the row's own done); the row wins ties. SidebarLaneStatus.applying reads the toggle and lanes inside each row's mapping, so a lane change or toggle flip re-maps that row. Tests cover all four lane states, toggle-off pass-through, and re-mapping on a lane change.
Refactors
- The lane-status code lives in
CmuxNextAgentLanesto keepCmuxNextAppwithin its source-line cap; the app reaches it viaSidebarRowFeed.laneRowSource, which replacesSidebarBridge's unusedCmuxNextActionsimport. - Tests compile against
AgentActivityLane.of(summary:); the removedtitle:parameter (#21794) no longer needs passing.