fix(session): remove step lifecycle noise in tool timeline (#629)

Hide step start/finish rows and split step grouping on reasoning boundaries so tool timelines mirror natural thought/tool cadence. Add docker-verified before/after screenshots for PR evidence.
This commit is contained in:
ben
2026-02-20 16:28:56 -08:00
committed by GitHub
parent 8e2d5f39ac
commit 49b1bba1cb
4 changed files with 30 additions and 18 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

View File

@@ -87,12 +87,7 @@ function latestStepPart(partsGroups: Part[][]): Part | undefined {
const parts = partsGroups[groupIndex] ?? [];
for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) {
const part = parts[partIndex];
if (
part.type === "tool" ||
part.type === "reasoning" ||
part.type === "step-start" ||
part.type === "step-finish"
) {
if (part.type === "tool" || part.type === "reasoning") {
return part;
}
}
@@ -187,7 +182,7 @@ export default function MessageList(props: MessageListProps) {
}
if (part.type === "step-start" || part.type === "step-finish") {
return props.developerMode;
return false;
}
if (part.type === "text" || part.type === "tool" || part.type === "agent" || part.type === "file") {
@@ -225,22 +220,22 @@ export default function MessageList(props: MessageListProps) {
const groupId = String((message.info as any).id ?? "message");
const groups = groupMessageParts(renderableParts, groupId);
const isUser = (message.info as any).role === "user";
const isStepsOnly = groups.length === 1 && groups[0].kind === "steps";
const isStepsOnly = groups.length > 0 && groups.every((group) => group.kind === "steps");
const stepGroups = isStepsOnly ? (groups as { kind: "steps"; id: string; parts: Part[] }[]) : [];
stepGroupCount += groups.reduce((count, group) => (group.kind === "steps" ? count + 1 : count), 0);
if (isStepsOnly) {
const stepGroup = groups[0] as { kind: "steps"; id: string; parts: Part[] };
const lastBlock = blocks[blocks.length - 1];
if (lastBlock && lastBlock.kind === "steps-cluster" && lastBlock.isUser === isUser) {
lastBlock.partsGroups.push(stepGroup.parts);
lastBlock.stepIds.push(stepGroup.id);
lastBlock.partsGroups.push(...stepGroups.map((group) => group.parts));
lastBlock.stepIds.push(...stepGroups.map((group) => group.id));
lastBlock.messageIds.push(messageId);
} else {
blocks.push({
kind: "steps-cluster",
id: stepGroup.id,
stepIds: [stepGroup.id],
partsGroups: [stepGroup.parts],
id: stepGroups[0].id,
stepIds: stepGroups.map((group) => group.id),
partsGroups: stepGroups.map((group) => group.parts),
messageIds: [messageId],
isUser,
});

View File

@@ -494,13 +494,14 @@ export function lastUserModelFromMessages(list: MessageWithParts[]): ModelRef |
}
export function isStepPart(part: Part) {
return part.type === "reasoning" || part.type === "tool" || part.type === "step-start" || part.type === "step-finish";
return part.type === "reasoning" || part.type === "tool";
}
export function groupMessageParts(parts: Part[], messageId: string): MessageGroup[] {
const groups: MessageGroup[] = [];
const steps: Part[] = [];
let textBuffer = "";
let stepGroupIndex = 0;
const flushText = () => {
if (!textBuffer) return;
@@ -508,33 +509,49 @@ export function groupMessageParts(parts: Part[], messageId: string): MessageGrou
textBuffer = "";
};
const flushSteps = () => {
if (!steps.length) return;
groups.push({ kind: "steps", id: `steps-${messageId}-${stepGroupIndex}`, parts: steps.splice(0, steps.length) });
stepGroupIndex += 1;
};
parts.forEach((part) => {
if (part.type === "text") {
flushSteps();
textBuffer += (part as { text?: string }).text ?? "";
return;
}
if (part.type === "agent") {
flushSteps();
const name = (part as { name?: string }).name ?? "";
textBuffer += name ? `@${name}` : "@agent";
return;
}
if (part.type === "file") {
flushSteps();
flushText();
groups.push({ kind: "text", part });
return;
}
if (part.type === "step-start" || part.type === "step-finish") {
return;
}
flushText();
if (part.type === "reasoning" && steps.length > 0) {
flushSteps();
}
steps.push(part);
});
flushText();
if (steps.length) {
groups.push({ kind: "steps", id: `steps-${messageId}`, parts: steps });
}
flushSteps();
return groups;
}