Conversation
…R 0001) When an agent run finishes, its intermediate steps (thinking + tool calls across consecutive messages) now collapse into a single expandable header row, so the final answer leads the conversation. - process-groups.ts: grouping rule (assistant messages without body text, with thinking and/or tool calls), span detection, master switch hook (localStorage 'llm-space-collapse-process-groups', on by default) - display-messages.ts: resolveDisplayRows folds settled runs into group rows; live streaming messages are never wrapped - process-group-header.tsx: collapsed header shows 'N tool calls', last tool name, and a warning state when a step failed - message-list-view.tsx: virtualizer treats a collapsed group as one fixed header row; id->display-index map keeps jump/autofocus/validation targets correct while member rows are hidden; navigator anchors to the group's first member - thread-store: expandedProcessGroupIds + toggleProcessGroupExpanded (store-only, never persisted); removeMessage cleans up stale ids - Settings → General: 'Collapse process steps' toggle (en/zh) - i18n: playground-labels processGroups block (en/zh + type) Tests: process-groups.test.ts (grouping boundaries, expand/collapse, virtualization row mapping).
MagicCube
left a comment
There was a problem hiding this comment.
Thank you for working on this! Collapsing completed process steps is a useful direction for long agent conversations, and keeping the grouping in the display layer preserves the underlying messages. I noticed three interaction cases worth adjusting before we merge; I've left examples inline to make them easier to check. Since LLM Space is also a debugging workbench, keeping unfinished steps and validation targets easy to find is especially helpful.
| const readonly = readonlyFromProps || isSnapshotView; | ||
| // Groups wrap only once a run has settled: while running/preparing the | ||
| // in-flight process steps stay expanded so streaming doesn't jump. | ||
| const groupingEnabled = collapseProcessGroups && status === "idle"; |
There was a problem hiding this comment.
Could we keep completed historical groups collapsed when a new run starts? This condition disables grouping for the entire conversation whenever the thread is preparing/running, so all previously collapsed groups become full message rows and then collapse again at the end. In a long conversation that changes the list height substantially and can disturb the reading position. Keeping the current run's steps visible while preserving older groups would make this much more stable. A regression test with a completed group followed by a new run would help cover it.
There was a problem hiding this comment.
Good catch — fixed in c0517de. Grouping is now purely structural instead of gated on status === "idle": a new run's in-flight steps form a trailing, unterminated span so they never wrap, while every older completed group keeps its collapsed header row across preparing/running. List height and reading position no longer shift when a run starts. Added the regression test you suggested (keeps older groups collapsed while a new run is in flight in process-groups.test.ts: completed group + running=true still yields one group row).
| ? (displayIndexByMessageId.get(autoFocusMessageId) ?? -1) | ||
| : -1; | ||
| const validationMessageId = runValidationIssue?.messageId ?? null; | ||
| const validationMessageIndex = validationMessageId |
There was a problem hiding this comment.
Could we also expand a group when a validation or autofocus target is inside it, then scroll to the actual message? Mapping hidden members to the header fixes the display index, but the target message is still unmounted. The existing effects therefore scroll to the header without revealing the field/error, and autofocus is consumed before its target can receive focus. Revealing the member first would preserve the existing error-fixing workflow.
There was a problem hiding this comment.
Fixed in c0517de. displayIndexByMessageId now maps only rendered rows, so a member hidden in a collapsed group resolves to nothing instead of its header — the effects used to scroll to the header and consume autoFocusMessageId before the target could ever mount. A new small effect expands the owning group (via findCollapsedGroupIdForMessage) and the existing scroll/autofocus effects then run against the real row on the next render, preserving the error-fixing workflow. Group ids are deduped per pass so two targets hiding in the same group don't cancel each other's toggle. Covered by the findCollapsedGroupIdForMessage tests.
| // A non-member message closes the run; the group is only kept when the | ||
| // closer is a user message or an assistant message with a result body. | ||
| const isTerminator = | ||
| message.role === "user" || |
There was a problem hiding this comment.
Could a user message end the candidate span without making it eligible for automatic collapse? For example, if a run is interrupted after a tool call and the user sends a follow-up, this branch now groups the unfinished steps even though no final answer was produced. That seems to differ from the stated goal of leaving runs without a result visible. Keeping those steps expanded would preserve the failure context; it would be useful to cover an interrupted run followed by a user message in the tests.
There was a problem hiding this comment.
Agreed — tightened in c0517de. A user message now only ends the candidate span; only an assistant message with a result body makes it eligible for automatic collapse, so an interrupted run's steps (tool call, no answer, user follows up) stay expanded as failure context — same treatment as a trailing run at the end of the list. Added both cases you asked for: a user message ends the span without collapsing unfinished steps and a later successful run groups without pulling in the interrupted one (the follow-up run still groups normally).
- Keep historical process groups collapsed while a new run is in flight: grouping is now purely structural instead of disabled for the whole conversation while preparing/running, so a long thread's list height and reading position no longer shift mid-run. A new run's steps stay expanded because they form a trailing, unterminated span. - Reveal a collapsed group before scrolling/focusing a run-validation or autofocus target inside it. Hidden members no longer map to their header row (they map to nothing), and a small effect expands the owning group so the target row mounts before the scroll/focus effects run. - A user message now only ends a candidate span instead of qualifying it: only an assistant message with a result body makes a group collapsible, so steps from an interrupted run (followed by a user message) stay expanded as failure context, matching the stated goal. Tests: interrupted-run grouping, older-group stability during a run, and findCollapsedGroupIdForMessage lookup.
|
This PR was auto-closed on 2026-09-13 when its head fork was deleted during a fork-network repair (the fork had been detached from upstream by an earlier ownership transfer, which silently broke PR creation; deleting and re-forking was the fix, but I failed to check for open PRs riding on that fork first — sorry, that was my miss). Re-submitted as #171 from the exact head SHA GitHub retained for this PR: the diff and review state are unchanged. |
Summary
Implements spec 0001: after an agent run finishes, its intermediate steps (thinking + tool calls spread across consecutive messages) collapse into a single expandable header row, so the final answer leads the conversation.
Grouping rule (derived view layer, no store/runtime changes):
Details
process-groups.ts: grouping rule, span detection, master switch hook (localStoragellm-space-collapse-process-groups, on by default)display-messages.ts:resolveDisplayRowsfolds settled runs into group rowsprocess-group-header.tsx: "N tool calls · Last: " with a warning state when a step failedmessage-list-view.tsx: virtualizer treats a collapsed group as one fixed header row; an id→display-index map keeps jump/autofocus/validation targets correct while member rows are hidden; message navigator anchors to the group's first memberthread-store:expandedProcessGroupIds+toggleProcessGroupExpanded(store-only, never persisted);removeMessagecleans up stale idsTest plan
process-groups.test.ts(grouping boundaries, expand/collapse, virtualization row mapping) — passespackages/uisuite (121 tests) and i18n tests passbun run typecheck:changed/bun run lint:changedcleanNotes
This PR is a dependency for the planned agent-layout PR (run timeline jumps reference result rows).