mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
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:
BIN
packages/app/pr/screenshots/steps-grouping-after-docker.png
Normal file
BIN
packages/app/pr/screenshots/steps-grouping-after-docker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
BIN
packages/app/pr/screenshots/steps-grouping-before-docker.png
Normal file
BIN
packages/app/pr/screenshots/steps-grouping-before-docker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 192 KiB |
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user