Repository navigation
Description
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
github-actions commentedon Aug 10, 2026
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
PostToolUsepayload containingtool_name/tool_inputreally 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.
PostToolUseis 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: "*" }(alsoPostToolUseFailureandPermissionRequest)packages/host-service/src/trpc/router/notifications/notifications.ts:46-49— thestartedTaskIdscomment independently confirms the cadence: "Startfires 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.shgreps out onlysession_id/sessionId(:20-24),resourceId(:25-28),thread-id(:33),hook_event_name/hookEventName/type(:38-51) andnotificationType(:60). The POST body at :110 carriesterminalId,eventTypeandagent{agentId,sessionId}— nothing else.Drop #1b — and the endpoint would strip it anyway.
hookInputatpackages/host-service/src/trpc/router/notifications/notifications.ts:19-23is a non-strict Zod object with three keys, so atoolNameadded to the script alone would be silently dropped server-side. Both sides need the change.Drop #2 —
mapEventTypecollapses it.packages/host-service/src/events/map-event-type.ts:50foldsPostToolUseintoStartalongside 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) hasstartedAt,lastEventAt,lastEventType. The issue's characterisation is precise:startedAtis reset only whenagentSessionIdchanges (store.ts:155-156), andlastEventAtis stamped on every event (store.ts:157). Neither is a per-turn clock. Persisted interminal_agent_bindingsatpackages/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-38gates the agents chip behindrunningAgents.length > 1;MIN_WORKSPACE_SIDEBAR_WIDTH = 220atapps/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); CopilotpostToolUse(agent-wrappers-copilot.ts:68-71,templates/copilot-hook.template.sh:15); Cursor'sCURSOR_MANAGED_EVENT_ARGS(agent-wrappers-cursor.ts:54-61) has no post-tool event; Codex isSessionStart/UserPromptSubmit/Stoponly (agent-wrappers-claude-codex-opencode.ts:153-157). One thing to note: Gemini and Copilot use separate template scripts, so editingnotify-hook.template.shcovers Claude/Codex/Droid/Kimi/Grok/Mastra (:7) but Gemini and Copilot each need their own edit. I could not verify the CopilottoolName/JSON-encoded-toolArgspayload shape from this repo — the template reads onlysession_idfrom 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
PreToolUseout — the code gives you a stronger reason than latency.mapEventType:68-70already mapsPreToolUse/preToolUse/pre_tool_usetoPermissionRequest, and Claude registersPermissionRequestwith 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 +
TodoWritefraction and no countdown. Worth adding: a turn clock derived from the Stop→Start edge can run forever, becauseStopis not guaranteed.store.ts:170-177documents that interrupts fire no hook at all, andclearWorkspaceStatusesexists precisely as an escape hatch for wedged state. The mitigation falls out of the same data — sincePostToolUseis a per-tool-call heartbeat,now - lastEventAtis 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_escapeis not sufficient for tool payloads.notify-hook.template.sh:105-107escapes only\and"via sed. ABashcommand 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 -oEagainst nestedtool_inputis fragile.tool_nameis a flat string and greps fine;tool_inputis a nested object whose values contain arbitrary quoting. Prefer classifying ontool_nameonly, plus at most a narrowly-anchoredfile_path.tool_input. Persisting raw tool input intoterminal_agent_bindingsmoves 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.store.recordEventemits"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 asetIntervalper row.Reproducing the current gap
Bashstep, and watch the sidebar row — the indicator is amber for the whole turn with no tool or duration.SUPERSET_DEBUG_HOOKS=1and read/tmp/superset-agent-hooks.log(notify-hook.template.sh:88-93). You'll see one line per tool call withevent=PostToolUse, and no tool identity — the debug line logs the same fields the POST does.A
bun:testunit repro is straightforward and worth including as a regression guard:expect(mapEventType("PostToolUse")).toBe("Start")already asserts the collapse atpackages/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, sinceStart/Stopcadence drives the working indicator and completion chime.Suggested direction
Carry tool identity alongside the collapsed lifecycle type rather than by widening
mapEventType: add an optionaltoolfield tohookInputand to thebroadcastAgentLifecycle/recordEventpayloads, classify it into a fixed label vocabulary, and addturnStartedAt(set on the Stop→Start edge) plus the optional progress fraction toTerminalAgentBinding. KeepStart/Stopsemantics untouched so the chime and indicator are unaffected. Gating behind an experimental flag following theworkspace-agents-rowpattern (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.Apurva3509 commentedon Aug 31, 2026
updates on this?