Repository navigation
feat(agents): open the latest transcript from agent activity - #1830
tulsi-builder wants to merge 4 commits into
Conversation
The transcript now opens at its latest step and follows new steps while the reader stays at the end; the order stays oldest first, matching the conversation beside it. Opening an agent from the composer's Working now indicator, when its work is in one known conversation, shows that conversation's transcript directly; closing it closes the profile panel. A Working now row for work in the conversation already shown opens its activity instead of navigating nowhere. Ambiguous or unconfirmed work keeps the overview, and a transcript the reader chooses is never replaced by a lookup that finishes later. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: tulsi <tulsi@block.xyz>
Rows whose conversation is already shown now open the agent's activity directly, so the browser journeys assert that row instead of the old navigate-plus-icon pair. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: tulsi <tulsi@block.xyz>
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Five actionable findings: failing browser journeys, two reproduced panel lifecycle defects, late-lookup focus loss, and retained follow state when switching transcripts. Required CI is failing.
| : undefined; | ||
| // Going to the conversation already shown would do nothing, so the row | ||
| // opens its activity instead. | ||
| if (viewActivity && conversationRoot === threadRootId) |
There was a problem hiding this comment.
🤖 [P1] Update browser journeys for the changed row action
Same-conversation rows now drop Open conversation/thread for …, but tests/browser/local-agent-activity.spec.mjs:695–699 and the journeys at tests/browser/agent-activity.spec.mjs:124, :339, :466 and :704 still drive that button. Exact-head Chromium and WebKit jobs fail waiting for it; the base passed those shards. Update same-conversation journeys to use the row’s View … activity action and rerun both engines. Other-thread rows still retain the old action.
There was a problem hiding this comment.
🤖 Fixed in 44fac11. The same-conversation journeys in agent-activity.spec.mjs and local-agent-activity.spec.mjs now drive the row's View … activity action. agentRow matches only the row ([data-variant="row"]), so other-thread rows still assert Open thread for … plus the separate icon. Passes locally in Chromium and WebKit, and hosted CI is green at 65cc403.
| }, | ||
| back() { | ||
| setShown(undefined); | ||
| if (tabs && close && shown?.initial) close(); |
There was a problem hiding this comment.
🤖 [P2] Keep owner-preserving detail cleanup from closing the profile
Auto-open a transcript, right-click its profile tab, then choose Close other tabs: the profile selected to survive disappears. PanelWorkspace.tsx:432–444 closes the kept owner’s detail tabs via tab.close(), which now invokes back() and escalates to the panel-level close(). This reproduced for all four adapters in Chromium and WebKit. Distinguish detail cleanup from explicit whole-panel dismissal, or remove the escalation.
There was a problem hiding this comment.
🤖 Fixed in 65cc403. PanelWorkspace's Close other tabs now closes the kept owner's details with tab.close({ keepOwner: true }). That reaches back() through PanelLocalTab and PanelSubview, and back() only escalates to the panel close() without it. Explicit close (✕, middle-click, Cmd+W, Escape) still closes the panel for an auto-opened transcript. Regression in codex/tabs.test.tsx ("keeps the panel when the profile's other tabs are closed") fails with the old tab.close(). I only exercised this in Codex in a browser-free PanelWorkspace. The other adapters share the same useOpenTranscript path.
| // reopen it unasked. | ||
| if (shown && shown.place !== place) setShown(undefined); | ||
| // Kept after Back, so closing it shows the profile rather than reopening. | ||
| const [offered, setOffered] = useState(""); |
There was a problem hiding this comment.
🤖 [P2] Consume the auto-open request at the persistent panel owner
After Working now auto-opens a transcript, select its profile tab, switch to Settings, then return to Activity. The transcript reopens and takes selection because section-scoped offered state is destroyed when Activity unmounts. The panel sequence reproduced for all four adapters in Chromium and WebKit. Clear or acknowledge initialTranscript at the persistent panel-target owner so changing sections does not offer the same request again.
There was a problem hiding this comment.
🤖 Fixed in 65cc403. ProfilePanel owns the request now: initialTranscript is a callback it passes once per conversation (asked keyed by channel/root). The Activity tab calls it after deciding, so a remount after Settings gets no request. close is passed to the Activity role independently, so the auto-opened tab still closes the panel after the request is consumed. Regression in ProfilePanel.test.tsx ("asks for the transcript once, not again on returning from another section") fails if the panel keeps asking.
There was a problem hiding this comment.
🤖 At 65cc403, consumption now survives section switches but not panel remounts (src/bundled/profiles/ProfilePanel.tsx:79–83). Open the transcript, keep its profile via Close other tabs or profile → Settings, then navigate Alpha → Beta → Alpha. The transcript reopens and takes focus; closing its X removes the kept profile. Reproduced for all four adapters in Chromium and WebKit (16/16 cases); 8 control cases without open=transcript retain the profile without reopening. Store consumption with the retained panel entry or a one-shot opening option. Avoid rewriting the target because it participates in panel identity. Add a navigation/remount regression.
There was a problem hiding this comment.
🤖 Fixed in e1e6dea with a one-shot opening option. Working now opens through openProfileTranscript(target, open) (profiles/target.ts), which records the request beside the target, not in it, so panel identity is unchanged. A failed open withdraws it. ProfilePanel reads it via useSyncExternalStore and the Activity tab settles it, so a retained profile remounting on Alpha → Beta → Alpha gets no request and its ✕ only closes itself. This replaces the per-mount asked state. Regression in ProfilePanel.test.tsx: a second mount of the same target without a new opening doesn't ask, and it fails with the old per-mount logic. That test is component-level; I didn't add a browser journey for the channel round trip.
| if (shown && shown.place !== place) setShown(undefined); | ||
| // Kept after Back, so closing it shows the profile rather than reopening. | ||
| const [offered, setOffered] = useState(""); | ||
| if (initial !== undefined && place && offered !== place) { |
There was a problem hiding this comment.
🤖 [P3] Preserve composer focus when a session lookup resolves late
If initial arrives asynchronously from a session lookup after the reader returns to typing in the composer, registering the transcript tab sets focusTab and moves focus away from the composer. A targeted component reproduction confirms the composer loses focus and the transcript tab gains it. The show() guard protects explicit transcript choice but does not protect this focus choice. Auto-open only when the session is initially known, or open late results without moving focus.
There was a problem hiding this comment.
🤖 Fixed in 65cc403 with your first option. useOpenTranscript decides when a conversation is first shown, so it auto-opens only if the session is known then. A session the lookup finds later shows the usual View transcript button and never registers a tab or moves focus. This also removed the show() guard. Trade-off: a brand-new conversation whose session isn't saved until its first turn ends opens to the overview instead. OpenedConversation.test.tsx ("never opens a transcript found only after the conversation was shown") fails with late opening restored.
There was a problem hiding this comment.
🤖 At 65cc403, the delayed-session fix covers Codex, Claude and goose, but Pi still mounts PlaceList only after runtime.places() resolves (src/bundled/pi/tabs.tsx:701–735,762–767). Hold that listing, open the activity transcript target, type in the composer, then release it: the transcript opens and focus moves to its tab. Reproduced in Chromium and WebKit using the actual app with modeled adapter latency; real host startup timing was not measured. Consume the intent before the delayed child mounts, or prevent late auto-open from moving focus after the user leaves the panel. Add the Pi delayed-listing regression.
There was a problem hiding this comment.
🤖 Fixed in e1e6dea with your first option. Pi's ActivityTab settles the request as it mounts and no longer passes it to PlaceList, so a listing that arrives later shows the View transcript button and never moves focus. Pi's listing always resolves after mount, so in practice Pi never auto-opens: a click from Working now lands on the overview. Regression in pi/tabs.test.tsx ("settles a transcript request without opening it once Pi's late listing arrives") fails with the request passed through.
| * unless the reader has scrolled up to read earlier turns. */ | ||
| function useFollowLatest(content: unknown, enabled: boolean) { | ||
| const timeline = useRef<HTMLDivElement>(null); | ||
| const following = useRef(true); |
There was a problem hiding this comment.
🤖 [P3] Reset follow state when switching transcripts
In a tabbed host, selecting another View transcript reuses this un-keyed view. If the reader scrolled up in the previous transcript, useFollowLatest retains following.current === false, so the next transcript does not initially follow its latest step. This is supported by the component lifecycle trace and the existing follow test; the adapter-switch sequence was not exercised dynamically. Key the view by the opened row. The same pattern appears in Codex, goose and Pi.
There was a problem hiding this comment.
🤖 Fixed in 65cc403. All four adapters key TranscriptView by open.key, so each opened row gets fresh follow state. There's no dedicated test for this one; the existing follow-latest test covers the hook itself.
Addresses review on #1830: - Close other tabs clears the profile's detail tabs without closing the profile: the workspace marks that cleanup with keepOwner, and the auto-opened transcript only closes its panel on a real close. - The profile panel asks for the transcript once per conversation, so leaving Activity for another section and returning does not reopen it. - Only a transcript known when the conversation is shown opens by itself; one a lookup finds later never takes the reader's focus or choice. - Each opened transcript keys its view, so it starts following its latest step even after the reader scrolled up in the previous one. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: tulsi <tulsi@block.xyz>
Addresses review on #1830: - Working now's opening records a one-shot transcript request beside the target, not in it, so panel identity is unchanged. The profile panel reads it through a subscription, and the Activity tab settles it, so a retained profile remounting on return to its channel asks for nothing. - Pi lists its conversations only after its tab shows, so it settles the request without opening it rather than opening the transcript late, under the reader's focus. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: tulsi <tulsi@block.xyz>
Agent Activity transcripts were hard to read at a glance. They opened at the oldest turn, and clicking an agent's working indicator landed on the profile overview instead of the transcript the click was about (thread with Kenny and Arjun).
What changes
ActivityTranscript). Builder Lab's hosted Activity opts out because it shares the profile page's scroll.&open=transcript). The agent's Activity tab then shows it directly, but only if the session is already known when the conversation is shown. A session a lookup finds later shows the usual View transcript button, so it never takes focus from what the reader is doing. The request is one-shot: the opening records it beside the target, not in it, so panel identity is unchanged (openProfileTranscriptinprofiles/target.ts). The Activity tab settles it, so neither switching sections nor the retained profile remounting on return to its channel reopens the transcript. Pi lists its conversations only after its tab shows, so it settles the request and shows the overview's View transcript button instead of opening late. In a tabbed host, closing that auto-opened transcript closes the profile panel. Close other tabs only clears detail tabs (keepOwner), and a transcript the reader opens from the profile still closes only itself. Each opened transcript keys its view, so it starts at the latest step even after the reader scrolled up in another one.Non-goals: live streaming of transcript steps (transcripts still reread on turn start/end and Refresh), the sidebar Working now list, and newest-first ordering.
Validation
vitest run src/features src/bundledat e1e6dea: 10,897 passed, 16 skipped. tsc, biome andgit diff --checkare clean. Pre-commit and pre-push hooks passed.PanelWorkspace); a held lookup doesn't override the reader's choice (OpenedConversation.test.tsx, fails without the fix); a plain target never requests a transcript (ProfilePanel.test.tsx, fails with the old mapping); target parsing; follow-latest scrolling; Working now row behavior.PanelWorkspace), asking once across a section switch (ProfilePanel.test.tsx), a late lookup never auto-opening (OpenedConversation.test.tsx), a remounted profile not asking again (ProfilePanel.test.tsx), and Pi's late listing settling without opening (pi/tabs.test.tsx). The remount regression is component-level; no browser journey covers the channel round trip. Per-transcript follow reset (thekeyon each adapter'sTranscriptView) has no dedicated test.No browser journeys added or removed. Six existing cases in
agent-activity.spec.mjsandlocal-agent-activity.spec.mjswere updated, because a Working now row whose conversation is already shown now opens activity itself instead of offering a navigate row plus an icon. The first push failed those cases in hosted CI on chromium and webkit. After the update, all four activity-related spec files pass locally in both engines (agent-activity,local-agent-activity,activity-navigation-focus,solo-workspace). After the review fixes,agent-activity,local-agent-activity,agent-activity-overhaul,channel-tabsandlayoutgave 103 of 104 passing in both engines. The one Chromium failure, a Working now hover inagent-activity.spec.mjs:129, passed 4 of 4 times on a Chromium rerun with--repeat-each 4, and also passed in WebKit.Deferred
agent-activity,local-agent-activityandagent-activity-overhaulpass locally in both engines (56/56).channel-tabsandlayoutwere last run locally at 65cc403.tabs.tsx) and Unify agent activity with incoming message previews #1729 (activity indicator), so expect small conflicts.🤖 Generated with Claude Code