Skip to content

feat(coc): drill into sub-agents from the Agents canvas (cascade dropdown + in-place read-only detail)#329

Merged
plusplusoneplusplus merged 7 commits into
mainfrom
coc/sub-agent-drill-in
Jun 14, 2026
Merged

feat(coc): drill into sub-agents from the Agents canvas (cascade dropdown + in-place read-only detail)#329
plusplusoneplusplus merged 7 commits into
mainfrom
coc/sub-agent-drill-in

Conversation

@plusplusoneplusplus

Copy link
Copy Markdown
Owner

Drill into an individual sub-agent from the chat's Agents canvas and view what it actually did — rendered identically to the main thread, read-only.

What's new

  • Cascading dropdown beside the Thread/Agents toggle: a left pane lists the run tree's depth levels (L0 orchestrator → L1 → L2 → L3…, only levels that exist); the right pane lists that level's agents. Pick an agent to open it; pick the orchestrator (L0) to return.
  • In-place, read-only sub-agent detail: the chosen sub-agent's conversation renders through the same ConversationArea / ConversationTurnBubble as the main thread (identical tool cards; nested sub-agents appear as drill-in Task cards), with a breadcrumb (Orchestrator → … → agent) and no follow-up input.
  • Deep-linkable: the selection rides the hash as ?agent=<id> alongside ?view=agents; a stale/invalid id clears itself and resets on chat switch.

Why it works with no backend change

Every tool call — not just Task — is captured flat in the main conversation and linked by parentToolCallId. So:

  • the run tree now nests by parentToolCallId (real depth; also improves the existing canvas + inspector), and
  • a sub-agent's full activity is reconstructed by collecting its descendant subtree. The thread renderer already re-nests by parentToolCallId and only nests when the parent is in the same turn, so feeding it a synthetic per-sub-agent turn (with the steps' parentToolCallId kept) re-roots the subtree and renders it identically.

Verified against real data: on a live L0→L3 chat the dropdown shows the right levels/agents and a sub-agent reconstructs its full 67-step subtree.

Also included

  • Node-card UI fix: a long role (e.g. general-purpose) no longer wraps/overflows the card (it ellipsis-truncates while the elapsed time stays), and a long-running/stuck agent's timer now formats compactly (m:ssHh MmDd Hh, so 7353:175d 2h). Shared by the canvas node and inspector.

Commits

  1. Nest the tree by parentToolCallId (+ extract shared agentToolCalls.ts).
  2. Helpers — flattenAgentLevels, buildSubAgentTurns, chatAgentHash.
  3. AgentCascadeMenu + read-only SubAgentDetailView.
  4. Wire into ChatDetail (state, hash sync, 3-way render precedence, composer suppression).
  5. Docs (coc-knowledge dashboard-spa.md).
  6. Node-card text clamp + duration formatting.

Testing

~50 new tests across the layers (tree nesting incl. cycle/orphan guards, level flattening, sub-agent turn reconstruction, hash round-trips, the cascade menu, the read-only detail view, duration formatting) plus a static-analysis wiring test for ChatDetail (too heavyweight to render). Build clean, 0 lint errors, full affected suite green (146 tests).

Reviewer notes

  • ChatDetail is verified via source-analysis tests (the repo's existing convention for it); ConversationArea is stubbed in the detail-view test to keep it isolated.
  • Known limitation: content-type timeline items carry no parentToolCallId, so a sub-agent's prose isn't attributed — its Task result shows as the closing message instead.
  • agentToolCalls.ts here was extracted from a local mixed commit (it had been swept into an unrelated prompt-builder refactor); that refactor is intentionally not part of this PR.

🤖 Generated with Claude Code

plusplusoneplusplus and others added 7 commits June 14, 2026 09:33
Sub-agents are captured flat in the main conversation, linked by
`parentToolCallId`. buildAgentRunTreeFromTurns used to attach every Task call
directly to the orchestrator (a 2-level tree); it now nests each Task under the
sub-agent that spawned it, giving the tree real depth (L0 → L1 → L2 → …). A Task
whose parent isn't another captured Task — or whose parent chain is cyclic —
falls back to the root, so a malformed chain can't recurse infinitely. Siblings
at every level stay ordered by start time, and the root status now reflects any
running/queued descendant at any depth.

This also improves the existing canvas + inspector, which already render nested
children. The generic tool-call readers (collectToolCalls, rawToolName, rawArgs,
nonEmptyArgs, asRecord, asString, parseTime, firstLine) move to a shared
agentToolCalls.ts so the upcoming per-sub-agent reconstructor reuses them; the
dedup merge now also preserves parentToolCallId across snapshots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eep-link

Three pure helpers powering the upcoming cascading dropdown + in-place sub-agent
detail view:

- agentLevels.ts: flattenAgentLevels(root) projects the tree into contiguous
  depth levels (L0…Ln) for the dropdown; findAgentNode / pathToAgent resolve a
  node and its breadcrumb chain by id.
- buildSubAgentTurns.ts: reconstructs one sub-agent's conversation as synthetic
  [userTurn(prompt), assistantTurn(steps + result)] by collecting its full
  descendant subtree via parentToolCallId. It keeps each step's parentToolCallId
  so the existing thread renderer re-roots the subtree (direct steps flat, nested
  sub-agents as Task cards) — identical tool-call rendering, no extra fetch.
- chatAgentHash.ts: read/applyAgentToHash for an `agent=<id>` deep-link param
  that coexists with `view=agents` (mirrors chatViewHash.ts).

All exported from the agent-canvas barrel. Covered by 29 new unit tests
(levels/find/path, subtree filtering incl. nested-task + cycle + background +
empty cases, and hash round-trips incl. coexistence with the view param).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two presentational components for the sub-agent drill-in:

- AgentCascadeMenu: a cascading dropdown for the chat top bar. Left pane lists
  the tree's depth levels (L0…Ln), right pane lists that level's agents; picking
  an agent opens it, picking the orchestrator (L0) returns to the thread. Outside-
  click + Escape close. Styled with the app's utility palette (it renders in the
  header, outside the .agent-canvas scope). Renders nothing when only L0 exists.

- SubAgentDetailView: a breadcrumb (orchestrator → … → agent, ancestors
  clickable) above the SAME ConversationArea the main thread uses, fed the
  synthetic turns from buildSubAgentTurns — identical tool-call rendering, no
  follow-up input. The task status is shaped to the sub-agent so a completed
  sub-agent doesn't show a live tail while the orchestrator still runs.

Both exported from the barrel. 15 new component tests (cascade open/hover/select/
escape/aria, breadcrumb navigation, turn/status prop shaping); ConversationArea
is stubbed in the detail-view test to keep it isolated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the cascading "Agents" dropdown and the in-place read-only sub-agent
detail view:

- selectedAgentId state, seeded from `?agent=<id>` and mirrored back into the
  hash alongside `?view=agents` (single replaceState composing both helpers).
- A three-way render precedence in the conversation column: sub-agent detail →
  agents canvas → thread. The detail branch renders SubAgentDetailView with the
  synthetic turns from buildSubAgentTurns and the breadcrumb path.
- The cascade menu mounts beside the Thread/Agents toggle (same hasSubAgents +
  loading/pending/variant gate). Selecting an agent forces the agents context so
  closing returns to the canvas; selecting the orchestrator (null) returns to it.
- Read-only: every FollowUpInputArea/no-session-notice guard now also requires
  !showSubAgentDetail, and the thread-only Ralph/Implement cards are guarded too.
- A stale/invalid `?agent=<id>` clears itself (parity with effectiveView's
  "can't strand on a deep-link" guarantee); selection resets on chat switch.

Covered by a static-analysis wiring test (mirrors the sibling ChatDetail tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tail view

Updates the Agents-view section of dashboard-spa.md: the run tree now nests via
parentToolCallId (real L0→L1→L2 depth, replacing the "arbitrary depth later"
note); the shared tool-call readers live in agentToolCalls.ts; and a new
paragraph covers the cascading dropdown (AgentCascadeMenu / flattenAgentLevels),
the in-place read-only sub-agent detail (SubAgentDetailView / buildSubAgentTurns
re-rooting the subtree through the same ConversationArea renderer), the
?agent=<id> deep-link, and the content-prose attribution limitation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two node-card glitches on the Agents canvas (seen on a stuck general-purpose
sub-agent):

- A long role like "general-purpose" wrapped to a second line and overflowed
  the fixed-height card, spilling below the status bar. The role · time row now
  stays on one line: the role ellipsis-truncates while the separator and elapsed
  time keep their size.
- A long-running/stuck agent rendered an unbounded minute count (e.g. 7353:17).
  Durations now collapse to compact units — `m:ss` under an hour, `Hh Mm` past
  an hour, `Dd Hh` past a day (so 7353:17 → "5d 2h"). The formatter is shared by
  the canvas node and the inspector (extracted to format.ts; sub-hour output is
  unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y detail

The pre-existing static-analysis test pins the exact FollowUpInputArea /
no-session-notice guard expressions. Wiring the sub-agent drill-in added
`&& !showSubAgentDetail` to those guards (the composer is suppressed in the
read-only detail view), so the pinned substrings shifted. Update the two
assertions to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@plusplusoneplusplus plusplusoneplusplus merged commit 78f8be6 into main Jun 14, 2026
36 checks passed
@plusplusoneplusplus plusplusoneplusplus deleted the coc/sub-agent-drill-in branch June 14, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant