feat(coc): Agents canvas — spatial sub-agent run tree for chat#326
Merged
Conversation
Foundation for a spatial "Agents" view of a chat's recursive sub-agent runs (ported from the coc-chat design). Pure, UI-free modules so the layout math and data adapter are unit-tested in isolation: - types.ts — AgentRunNode tree (orchestrator root → sub-agent runs). - layout.ts — tidy left→right tree layout (buildLayout), curved edge paths (edgePath), and per-depth spine colors (spineVars), ported verbatim from the prototype so spatial output matches. - buildAgentRunTree.ts — adapts real conversation turns into the tree: the orchestrator is the root, each `Task` tool call becomes a sub-agent child (name/role from args, status/timing from the call), deduped across toolCalls + timeline and ordered by start time. Tests cover layout geometry/edges/spine cycling and the adapter's field mapping, prompt-name fallback, dedup, root-status derivation, and ordering. No UI is wired yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pannable/zoomable spatial tree view, ported from the coc-chat design and adapted to the app: - AgentCanvas.tsx — renders the run tree from buildLayout: curved SVG edges (animated for running, dashed for queued) + absolutely-placed HTML node cards. Each card shows a role glyph, name, role · live elapsed, a spawn-count pill, a status dot, and a progress bar. Pan/ zoom is delegated to the shared useZoomPan hook; auto-fit on mount and on resize until the user takes over, with a re-arming Fit button. A live 1s clock ticks running nodes' elapsed time; an empty state shows when the chat spawned no sub-agents. - icons.tsx — line-icon set (roles, spawn, zoom, view nav) ported from the design, plus a keyword→role glyph picker for free-form agent types. - agent-canvas.css — design's canvas styles, namespaced under `.agent-canvas` with light/dark token sets (dark via `.dark`). - index.ts — barrel for the feature. Render tests cover node rendering, the spawn pill, empty state, selection highlight, onSelect, the zoom toolbar, and status flags. Not wired into the chat yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a Thread | Agents segmented toggle to the chat top bar and renders the sub-agent canvas as a second view, leaving the thread untouched. - ChatViewToggle — segmented control (Thread / Agents) styled with the app's light/dark tokens; exposed through a new optional `viewToggle` slot on ChatHeader. - ChatDetail — owns `view` state (resets to thread on chat switch). Builds the agent tree from the loaded turns via buildAgentRunTreeFromTurns and, in Agents mode, swaps the ConversationArea inner row for <AgentCanvas>, keeping the composer/ scratchpad and hiding thread-only flow cards (Ralph start, Implement plan). The toggle is hidden in the floating variant and while loading/pending. - Node click → findTurnIndexForRun maps the run back to its issuing turn, switches to the thread, and scrolls to it. Tests: findTurnIndexForRun (toolCalls/timeline/turnIndex/missing) and ChatViewToggle (render, aria-pressed, onChange). build:client passes; no new tsc/lint errors. Knowledge doc (dashboard-spa.md) updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live (SSE) tool calls carry `toolName`, but the persisted forge read model (loaded by refreshConversation after completion) carries `name` (and may use `parameters` instead of `args`). The agent-tree adapter read `tc.toolName` directly, so once a chat finished and its turns were refreshed from the server, `Task` calls were no longer recognized and every sub-agent vanished from the canvas — leaving just the root. Detect the tool name via `toolName ?? name` and read args via `args ?? parameters`, so sub-agents render identically mid-run and after completion. Tests cover the persisted (`name`) shape and `parameters` fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The canvas opened auto-fit (scaled down to fit the whole tree). Default to 100% zoom with the content centered in the viewport instead, so nodes render at full size on open and the user pans to explore. - useZoomPan: add `centerContent(scale = 1)` — sets the given scale and centers content in the container (additive; existing consumers unaffected). - AgentCanvas: use centerContent(1) for the default/auto view (mount, tree growth, resize) until the user takes over. The Fit button still zooms to fit the whole tree and now sticks (counts as an interaction) rather than re-arming auto-center. Tests cover centerContent (centering math, no-container no-op, scale clamping). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Thread/Agents view is now shareable/bookmarkable. In the main inline chat context the active view rides as a `?view=agents` query param on the chat hash (`#repos/<ws>/<tab>/<taskId>?view=agents`): - chatViewHash.ts — pure read/apply helpers for the `view` param (preserve path, leading #, and other params). - ChatDetail — initializes `view` from the hash on mount (so a shared URL reopens straight into the canvas) and mirrors it back via history.replaceState on toggle. Gated to variant 'inline' & non-standalone. The per-chat reset honors a deep-linked view on first mount (ChatDetail is keyed per task, so the initializer re-reads the hash for each chat). - Router — `parseActivityDeepLink` strips a trailing `?query` before extracting the taskId, so the new param never corrupts chat routing. Tests cover chatViewHash (read/apply/round-trip, param preservation) and the Router taskId query-strip. dashboard-spa.md updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The toolbar's % label is now a dropdown of preset zoom levels (25/50/75/100/150/200%) plus "Fit to screen". Picking a level zooms about the viewport center. - useZoomPan: add `zoomTo(scale)` — zooms to an exact scale while keeping the world point under the viewport center fixed (additive). - AgentCanvas: the % becomes a menu button; the active level is marked; the menu closes on pick or outside click. Picking a level counts as a user interaction (stops auto-centering). Tests cover zoomTo (center-fixed math, clamping, no-container) and the menu (opens from %, lists presets + Fit, closes on pick). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking a sub-agent node now opens a detail panel instead of jumping back to the thread. - AgentInspector — right-side panel showing the run's role, status, elapsed/duration, the task prompt it was handed, its result/conclusion, and its child runs (clickable to drill in). An explicit "Open in thread" button preserves the jump-to-turn action. - AgentCanvas owns selection internally: clicking a sub-agent opens the inspector and highlights the node; clicking the orchestrator root or the close button dismisses it; the legend hides while it's open. New `onOpenInThread` prop replaces the old `onSelect`/`selectedId` props. - buildAgentRunTree now captures each run's `prompt` (task) and `result` for the inspector; duration helpers handle a 0 timestamp. - ChatDetail passes `onOpenInThread` (the former jump-to-turn handler); the internal selected-agent state is gone. - icons: add Clock + X. Tests cover the inspector (status/duration/task/result, child drill-in, close, root has no result/open-in-thread) and the canvas wiring (open on sub-agent click, highlight, close on root/close, onOpenInThread). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Task tool's args carry richer fields than were surfaced. Capture and
display them:
- buildAgentRunTree: read `name` (the agent's name) as the node title
(falling back to description/prompt), plus `model`, `mode`, and keep
`description` when it adds something beyond the name.
- AgentInspector: add a details list (Model / Mode / Summary) under the
status meta. Type is the role shown in the header, name is the title.
So a node like { name: "time-agent-1", agent_type: "explore",
model: "claude-sonnet-4.6", mode: "background", description: "Query
current time" } now reads clearly in the panel.
Tests cover the new arg capture (name title, model/mode/description) and
the inspector rendering them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… args Each Task call appears three times in a completed conversation: toolCalls (full args), timeline tool-start (full args), and timeline tool-complete (same terminal status, but EMPTY args). The dedup preferred the latest equal-rank snapshot — the empty-args tool-complete — so every node fell back to "sub-agent"/"agent" with no model/mode. collectToolCalls now keeps whichever snapshot has non-empty args (nonEmptyArgs helper), so the name/type/model/mode survive. Verified against the live "3 subagents" chat: nodes now resolve to time-agent-1/2/3 · explore · claude-sonnet-4.6 · background. Regression test replicates the three-snapshot shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Router's hashchange handler split the raw hash into path parts without removing a trailing `?query`, so a deep-link like `#repos/<ws>/activity/<taskId>?view=agents` parsed the task id as `<taskId>?view=agents` — the wrong id, which broke landing directly on the Agents view (and any other `?`-carrying chat deep-link). Strip the query once where the handler reads the hash, so every routed segment (repoId, sub-tab, taskId) is clean. The query stays on `location.hash`, so components that read it directly (ChatDetail's `?view`, TaskPreview's `?mode`) still work. Mirrors the existing `parseActivityDeepLink` query-strip (already tested). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an Agents view to the CoC chat: a pannable / zoomable spatial tree of a chat's recursive sub-agent runs, toggled from a Thread | Agents switch in the chat header. Ported from the
coc-chatdesign handoff. The existing thread rendering is untouched — this is purely additive (a second view + a toggle).All new UI lives under
packages/coc/src/server/spa/client/react/features/chat/agent-canvas/. Distinct from the existing co-edited CanvasPanel side panel.What's included (11 commits)
Core view
Thread | Agentssegmented toggle inChatHeader(new optionalviewToggleslot);ChatDetailswaps the conversation row for<AgentCanvas>in Agents mode and hides thread-only flow cards. Thread untouched.AgentCanvas— curved SVG edges + node cards (role glyph, name, live elapsed, spawn-count pill, status dot, progress bar). Pan/zoom via the shareduseZoomPanhook.buildLayout/edgePath/spineVars(ported 1:1) andbuildAgentRunTreeFromTurns, which derives the tree from the conversation'sTasktool calls — no extra fetch.Refinements (from review feedback)
useZoomPan.centerContent); Fit button still fits the whole tree.useZoomPan.zoomTo(zooms about the viewport center).?view=agentson the chat hash, read on mount and written on toggle, so a shared/bookmarked URL opens straight into Agents.Fixes folded into the series
name/parameters(vs livetoolName/args); the adapter now reads both.tool-completeoften carries empty args, which was wiping out name/model/type.?querybefore parsing the path, so…/activity/<taskId>?view=agentsno longer parses the query into the task id.Why
The chat couldn't visualize recursive sub-agent runs. This makes the orchestrator → sub-agent tree legible at a glance and drillable, building on the existing chat UI as the design requested.
Testing
chatViewHash,useZoomPancenter/zoomTo,AgentCanvas,AgentInspector,ChatViewToggle, Router query-strip).npm run build:clientpasses; 0 new tsc errors (pre-existing client-tsconfig baseline unchanged) and 0 lint errors in changed files.time-agent-1/2/3 · explore · claude-sonnet-4.6 · background.Reviewer notes
Taskcalls);AgentRunNodesupports arbitrary depth, so deeper recursion (via child processes) can be layered on later without touching the canvas.dashboard-spa.mdupdated; no changeset added (repo has no active per-PR changeset convention).🤖 Generated with Claude Code