Skip to content

web-shell: sidebar shows nothing about a workspace beyond its name — add inline overview, a full workspace menu, and a per-workspace overview endpoint #10399

Description

@wenshao

Problem

The Web Shell sidebar treats a workspace as a folder heading and nothing more. On main (d853f09) a workspace row shows the basename, an untrusted / read-only badge, and a git pill when a branch is known — and that is all. There is no way to tell from the sidebar how many workspaces are registered, how many sessions each has (or how many are running), whether its MCP servers are connected, or what skills / extensions / channels / context files it carries. The menu has exactly one item, Remove workspace.

The data is not the gap. The daemon already exposes every one of those facts per workspace (/workspaces/:w/{mcp,skills,extensions,channels,git,memory,hooks,sessions}), and the SDK wraps them (workspaceByCwd(cwd).workspaceMcp() etc.). The sidebar consumes only the seven fields of DaemonWorkspaceCapability (sdk-typescript/src/daemon/types.ts:108-118).

Concretely:

  1. Row carries almost no informationWorkspaceSection.tsx:493-560 renders name + badges + git pill; no full path, no session counts, no MCP / Skills / Extensions hint.
  2. Only management action is RemoveWebShellSidebar.tsx:5438-5478. updateWorkspace (rename), initWorkspace (reload runtime) and requestWorkspaceTrustChange exist in the SDK with zero web-shell call sites.
  3. MCP / Skills pages are implicitly bound — reachable only via /mcp, /skills or the global Plugins nav; openPanel() (App.tsx:5371) takes no workspace, and the pages read the provider-bound cwd. With several workspaces registered the user cannot tell whose MCP list they are looking at.
  4. No workspace overview — no count / limit, SessionOverviewPanel is a session grid with no per-workspace roll-up, and GET /daemon/status?detail=full only covers the bound workspace (daemon-status.ts:1137).
  5. No aggregate endpoint — showing counts today means 8 requests per workspace, and MCP / Skills / Extensions return initialized:false placeholders while no ACP child is live.

Plan

Three layers; each is independently shippable and the order is a real dependency chain.

A — inline info + full menu (one frontend PR, no protocol change)

  • Header: ● running / total session counts (kept in the collapsed state), full path line when expanded (tooltip when collapsed), Projects N count in the section header.
  • Expanded state: a chip row main ↑2 ·3 · MCP 3/4 · Skills 12 · Ext 4 · Ch 2 · Ctx 3. MCP chip turns warning-colored when any server is disconnected / errored. Chips collapse to one icon below ~250px sidebar width. Placeholders are never rendered as 0 — while the runtime is not initialized the chip shows "—".
  • menu: Rename · Copy path · New task · New task in worktree · Manage: MCP servers / Skills / Extensions / Channels / Settings (with live counts) · Reload runtime · Trust… · Remove workspace.
  • New files: sidebar/WorkspaceOverview.tsx, sidebar/WorkspaceMenu.tsx, hooks/useWorkspaceOverview.ts (fan-out via workspaceByCwd, gated on expanded + visible, reusing the 2s / 30s poll tiers, request-deduped per cwd). Each with its own tests rather than growing the existing 1.6k / 5.2k-line sidebar test files.
  • New sidebar prop workspaceOverview?: false | { items?: (...)[] } so embedders can turn it off. Untrusted workspaces skip MCP / Skills fetches; locked-workspace and kind: 'live' rows keep their current rendering.

B — workspace-bound management pages + Workspaces overview (two frontend PRs)

  • B1: openPanel(panel, { workspaceCwd }); MCP / Skills / Extensions / Channels / Settings pages show which workspace they are bound to and offer a switcher (reusing the composer workspace selector). Chips and the Manage menu group open the page for that workspace. Locked mode pins the cwd and hides the switcher.
  • B2: new App panel 'workspaces' — a table (name, path, trust, running / total sessions, MCP health, branch + dirty state, last activity, actions), styled after SessionOverviewPanel, reusing the existing removal dialog. Entry: Manage workspaces… at the end of the Projects section + optional footer item.

C — GET /workspaces/:w/overview (one daemon + SDK PR)

A read-only projection advertised as feature workspace_overview:

{ "runtime": { "acpLive": true, "initialized": true },
  "sessions": { "total": 14, "running": 2, "needsApproval": 1 },
  "mcp": { "configured": 4, "connected": 3, "error": 1, "disabled": 0, "discoveryState": "completed" },
  "skills": { "total": 12, "enabled": 11 }, "extensions": { "total": 4, "enabled": 4 },
  "channels": { "configured": 2, "running": 1 },
  "git": { "branch": "main", "ahead": 2, "behind": 0, "dirty": 3 },
  "memory": { "fileCount": 3, "ruleCount": 27 }, "hooks": { "count": 5, "disabled": false },
  "asOf": "" }

Server side this is the existing parallel fan-out in daemon-status.ts:1141-1166 parameterized by workspace and trimmed to summaries, with snapshot caching in the invalidateWorkspaceSkillsSnapshot style. The frontend hook switches to the single request when the feature is advertised and falls back to the layer-A fan-out otherwise. This layer waits for the workspace-runtime stack (#7308#7309#7310#7311) to land so the overview is defined as a projection of the runtime rather than a second resolver.

Risks

Non-goals

  • Worktree list per workspace (no daemon route; worktrees are session metadata only).
  • Custom slash command counts (session-scoped only).
  • Merge-back / worktree management UI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions