Skip to content

Show what each agent is currently doing in the sidebar (activity label + elapsed timer) #6323

Open

Description

@Apurva3509

What problem are you trying to solve?

When you're running several agents at once, the sidebar tells you that an agent is busy but never what it's busy with. The amber spinner is binary: working or not. To find out whether an agent is running tests, editing UI, or stuck three minutes into a shell command, you have to click into the workspace and read the terminal — which defeats the point of watching ten workspaces from one list.

There's also no sense of duration. lastEventAt is a heartbeat and startedAt is the session clock, so nothing on screen answers "how long has this one been going?"

Proposed solution

Add a per-row activity readout to the v2 dashboard sidebar:

  • A compact right-hand column: a tool-kind icon plus an elapsed timer for the current turn (~56px, so it doesn't crowd the workspace name at the 220px minimum width).
  • A phase label on the existing chips line beneath the row — "Running tests", "Building UI · Sidebar.tsx", "Opening a PR".
  • A progress fraction when the agent published one.

The interesting part is that Superset already receives everything needed and throws it away. CLAUDE_MANAGED_EVENTS registers PostToolUse with matcher *, so notify.sh is invoked on every tool call with tool_name and tool_input in the payload — but the script only greps out hook_event_name, and mapEventType then collapses PostToolUse into Start before storage. No new hooks are needed to get a real activity label; the data is on the wire today.

Two design points I'd want your read on before proposing anything:

1. No PreToolUse. GROK_MANAGED_HOOK_EVENTS documents that it's deliberately excluded because it blocks the tool call. That constraint holds here: a phase label spans many tool calls, so lagging one PostToolUse behind is invisible in practice. Worth confirming you'd want it kept that way rather than trading latency for present-tense precision.

2. ETA is not honestly derivable, and I'd rather not fake one. Nothing persisted predicts turn length, and the variance is dominated by a single unpredictable tail — one Bash is ls, the next is a four-minute test suite. An average over completed tool calls produces a confident wrong number. The one real signal is the agent's own TodoWrite payload, which gives a measured 3/7 rather than a guess. My preference is to ship the fraction and the elapsed time and no countdown; if a rough projection is wanted, it should be clearly labelled as extrapolation and kept out of the always-visible column.

Agent coverage is uneven and I think that should be explicit rather than papered over: Claude, Droid, Kimi, Grok and Mastra share notify-hook.template.sh and get the full label; Gemini's AfterTool carries the same tool_name/tool_input shape; Copilot's camelCase postToolUse uses toolName with a JSON-encoded toolArgs string; Cursor registers no post-tool event at all today, and Codex registers only SessionStart/UserPromptSubmit/Stop, so both stay at the coarse indicator unless their registrations change.

Happy to open a PR — I have this working end-to-end behind an experimental flag, following the workspace-agents-row pattern, with the hook script, a host-service migration, the classifier and the sidebar UI. Wanted to agree the approach first per CONTRIBUTING, particularly on the ETA question, since that's a product call rather than a technical one.

Workarounds you've tried

Clicking into each workspace and reading the terminal, which is what the sidebar is supposed to save you from. The existing agents chip helps a little — it shows how many agents are live and their coarse status — but carries no activity or timing.

Activity

  1. github-actions commented on Aug 10, 2026

    @github-actions
    Contributor

    Summary

    This is a feature request, not a bug report, so instead of a root cause I verified the report's technical premises against the code. Every factual claim in the issue checks out, including the central one: the PostToolUse payload containing tool_name/tool_input really does arrive on every tool call today and is discarded twice over before anything could display it. Below are the file references, the two places the data is dropped, and a few implementation hazards the issue doesn't mention that I'd want settled before a PR.

    Affected code

    The data is on the wire. PostToolUse is registered with matcher *, so the hook fires on every tool call:

    • apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts:85 — PostToolUse: { matcher: "*" } (also PostToolUseFailure and PermissionRequest)
    • packages/host-service/src/trpc/router/notifications/notifications.ts:46-49 — the startedTaskIds comment independently confirms the cadence: "Start fires on every agent turn and tool use"

    Drop #1 — the hook script never extracts it. apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh greps out only session_id/sessionId (:20-24), resourceId (:25-28), thread-id (:33), hook_event_name/hookEventName/type (:38-51) and notificationType (:60). The POST body at :110 carries terminalId, eventType and agent{agentId,sessionId} — nothing else.

    Drop #1b — and the endpoint would strip it anyway. hookInput at packages/host-service/src/trpc/router/notifications/notifications.ts:19-23 is a non-strict Zod object with three keys, so a toolName added to the script alone would be silently dropped server-side. Both sides need the change.

    Drop #2 — mapEventType collapses it. packages/host-service/src/events/map-event-type.ts:50 folds PostToolUse into Start alongside eleven other aliases. Downstream, status is a pure function of that one string: deriveTerminalAgentStatus (apps/desktop/src/renderer/hooks/host-service/useTerminalAgentStatuses/deriveTerminalAgentStatus.ts:17-23) — which is exactly why the indicator is binary.

    No timing field exists. TerminalAgentBinding (packages/host-service/src/terminal-agents/types.ts:24-35) has startedAt, lastEventAt, lastEventType. The issue's characterisation is precise: startedAt is reset only when agentSessionId changes (store.ts:155-156), and lastEventAt is stamped on every event (store.ts:157). Neither is a per-turn clock. Persisted in terminal_agent_bindings at packages/host-service/src/db/schema.ts:46-64, so persisted fields do need a schema change + generated migration, as the issue anticipates.

    UI surfaces are where the issue says. DashboardSidebarWorkspaceChips.tsx:37-38 gates the agents chip behind runningAgents.length > 1; MIN_WORKSPACE_SIDEBAR_WIDTH = 220 at apps/desktop/src/renderer/stores/workspace-sidebar-state.ts:6 (default 280, max 400), so the ~56px budget is sound at the true minimum.

    Coverage table is accurate. Gemini AfterTool (agent-wrappers-gemini.ts:59, templates/gemini-hook.template.sh:14); Copilot postToolUse (agent-wrappers-copilot.ts:68-71, templates/copilot-hook.template.sh:15); Cursor's CURSOR_MANAGED_EVENT_ARGS (agent-wrappers-cursor.ts:54-61) has no post-tool event; Codex is SessionStart/UserPromptSubmit/Stop only (agent-wrappers-claude-codex-opencode.ts:153-157). One thing to note: Gemini and Copilot use separate template scripts, so editing notify-hook.template.sh covers Claude/Codex/Droid/Kimi/Grok/Mastra (:7) but Gemini and Copilot each need their own edit. I could not verify the Copilot toolName/JSON-encoded-toolArgs payload shape from this repo — the template reads only session_id from stdin (:7) and takes the event from argv — so treat that as an upstream-API claim to confirm against a live payload.

    On the two design questions

    1. Keeping PreToolUse out — the code gives you a stronger reason than latency. mapEventType:68-70 already maps PreToolUse/preToolUse/pre_tool_use to PermissionRequest, and Claude registers PermissionRequest with matcher * (:87). That name is semantically spoken for: routing tool calls through it would flip live agents into "permission" (deriveTerminalAgentStatus:18) and paint a false "blocked on you" state. So the answer is yes, keep it excluded — not merely because it blocks, but because adopting it would require disentangling it from permission state first.

    2. The ETA argument holds, with a supporting detail. Agreed on shipping elapsed + TodoWrite fraction and no countdown. Worth adding: a turn clock derived from the Stop→Start edge can run forever, because Stop is not guaranteed. store.ts:170-177 documents that interrupts fire no hook at all, and clearWorkspaceStatuses exists precisely as an escape hatch for wedged state. The mitigation falls out of the same data — since PostToolUse is a per-tool-call heartbeat, now - lastEventAt is a real staleness signal, so cap or grey the timer past a threshold rather than letting it count into hours.

    Hazards to resolve in the PR

    • json_escape is not sufficient for tool payloads. notify-hook.template.sh:105-107 escapes only \ and " via sed. A Bash command or file path containing a newline or control character will produce a malformed JSON body and the POST will 400 — silently, since the script only inspects the status class (:123-125) and then falls through to the v1 path. Any extracted label must be sanitised to a safe subset (basename / fixed vocabulary) and length-capped before interpolation.
    • grep -oE against nested tool_input is fragile. tool_name is a flat string and greps fine; tool_input is a nested object whose values contain arbitrary quoting. Prefer classifying on tool_name only, plus at most a narrowly-anchored file_path.
    • Prefer sending a classified label over raw tool_input. Persisting raw tool input into terminal_agent_bindings moves shell command text and file paths from the terminal into a durable store and onto an always-visible surface. The issue's classifier approach avoids this; making the wire field a closed vocabulary rather than a passthrough string is worth stating as a constraint.
    • Render cost. store.recordEvent emits "change" per event (store.ts:163) and that is already per-tool-call. Drive all row timers from one shared interval at the container rather than a setInterval per row.

    Reproducing the current gap

    1. Open a workspace, run an agent with a long Bash step, and watch the sidebar row — the indicator is amber for the whole turn with no tool or duration.
    2. Confirm the payload is arriving and being discarded: set SUPERSET_DEBUG_HOOKS=1 and read /tmp/superset-agent-hooks.log (notify-hook.template.sh:88-93). You'll see one line per tool call with event=PostToolUse, and no tool identity — the debug line logs the same fields the POST does.

    A bun:test unit repro is straightforward and worth including as a regression guard: expect(mapEventType("PostToolUse")).toBe("Start") already asserts the collapse at packages/host-service/src/events/map-event-type.test.ts:20, so the lossy step is pinned by an existing test — whatever design you land on must add a parallel channel rather than change that mapping, since Start/Stop cadence drives the working indicator and completion chime.

    Suggested direction

    Carry tool identity alongside the collapsed lifecycle type rather than by widening mapEventType: add an optional tool field to hookInput and to the broadcastAgentLifecycle/recordEvent payloads, classify it into a fixed label vocabulary, and add turnStartedAt (set on the Stop→Start edge) plus the optional progress fraction to TerminalAgentBinding. Keep Start/Stop semantics untouched so the chime and indicator are unaffected. Gating behind an experimental flag following the workspace-agents-row pattern (apps/desktop/src/renderer/stores/workspace-agents-row.ts, whose header documents the teardown steps) is the right call, and the uneven-coverage stance — coarse indicator for Cursor and Codex rather than a synthesised label — matches how those registrations actually look today.

  2. Apurva3509 commented on Aug 31, 2026

    @Apurva3509
    Author

    updates on this?

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions