fix: harden presentation replay state

This commit is contained in:
lda
2026-07-11 23:50:31 +07:00 Verified
parent 1dcc5b5f58
commit bd67f260dc
10 changed files with 77 additions and 27 deletions
@@ -142,11 +142,7 @@ describe("AssistantOperatorThread", () => {
it("scrolls the active authoring group into the dock viewport", async () => {
const setScrollTop = vi.fn();
Object.defineProperty(HTMLDivElement.prototype, "scrollTop", {
configurable: true,
get: () => 0,
set: setScrollTop,
});
const originalDescriptor = Object.getOwnPropertyDescriptor(HTMLDivElement.prototype, "scrollTop");
const messages: ReadonlyArray<AgentMessage> = [
{
id: "authoring-draft-tools",
@@ -158,16 +154,47 @@ describe("AssistantOperatorThread", () => {
},
];
render(
<AssistantOperatorThread
mode="dock"
surface="dock"
messages={messages}
activeToolGroupId="authoring-draft"
/>,
);
try {
Object.defineProperty(HTMLDivElement.prototype, "scrollTop", {
configurable: true,
get: () => 0,
set: setScrollTop,
});
render(
<AssistantOperatorThread
mode="dock"
surface="dock"
messages={messages}
activeToolGroupId="authoring-draft"
/>,
);
await waitFor(() => expect(setScrollTop).toHaveBeenCalledWith(0));
await waitFor(() => expect(setScrollTop).toHaveBeenCalledWith(0));
} finally {
if (originalDescriptor) {
Object.defineProperty(HTMLDivElement.prototype, "scrollTop", originalDescriptor);
} else {
delete (HTMLDivElement.prototype as { scrollTop?: number }).scrollTop;
}
}
});
it("pairs a lone tool call with its result", () => {
const messages: ReadonlyArray<AgentMessage> = [
{
id: "assistant-tool-result",
role: "assistant",
parts: [
{ type: "tool-call", call: { id: "call-1", name: "readRunTrace", input: {} } },
{ type: "tool-result", result: { callId: "call-1", name: "readRunTrace", status: "success", output: { frames: 3 } } },
],
},
];
const { container } = render(<AssistantOperatorThread mode="dock" messages={messages} />);
expect(container.querySelector('[data-slot="tool-fallback-result"]')).toBeInTheDocument();
expect(screen.getByText(/"frames": 3/)).toBeInTheDocument();
});
it("renders structured tool results through the generated fallback result slot", () => {
@@ -157,7 +157,7 @@ const AssistantMessageBody = ({
const part = parts[index]!;
if (part.type === "text") {
rendered.push(
<p key={`text-${part.text}`} style={{ whiteSpace: "pre-line" }}>{part.text}</p>,
<p key={`text-${index}`} style={{ whiteSpace: "pre-line" }}>{part.text}</p>,
);
index += 1;
continue;
@@ -174,9 +174,14 @@ const AssistantMessageBody = ({
const logicalTools = calls.length > 0 ? calls : toolRun;
if (logicalTools.length === 1 && !messageId.startsWith("authoring-")) {
const tool = logicalTools[0]!;
const pairedResult = tool.type === "tool-call"
? toolRun.find((candidate): candidate is Extract<ToolRenderPart, { readonly type: "tool-result" }> =>
candidate.type === "tool-result" && candidate.toolCallId === tool.toolCallId)
: undefined;
rendered.push(
<div key={`tool-${logicalTools[0]!.toolCallId ?? logicalTools[0]!.toolName}`}>
{renderContentPart(logicalTools[0]!, submitApproval, requestRevision)}
<div key={`tool-${tool.toolCallId ?? tool.toolName}`}>
{renderContentPart(tool, submitApproval, requestRevision, undefined, pairedResult)}
</div>,
);
continue;