diff --git a/packages/web/e2e/subagent.spec.mjs b/packages/web/e2e/subagent.spec.mjs index eb4d353..bbffc34 100644 --- a/packages/web/e2e/subagent.spec.mjs +++ b/packages/web/e2e/subagent.spec.mjs @@ -84,6 +84,22 @@ test("subagent card survives a reload; child session title is generated from its // Under live streaming, the child session card is nested inside the run_subagent tool card. await openSubagent(); + // --- DOM order: the subagent expansion renders BELOW the tool's own content --- + // Expand the run_subagent tool card itself so its arguments block is in the DOM, then assert + // the child-session card comes after it in document order (it used to render between the tool + // header and the expanded arguments/output). + const toolRow = page.locator("button[aria-expanded]").filter({ hasText: "run_subagent" }).first(); + await toolRow.click(); + await expect(toolRow).toHaveAttribute("aria-expanded", "true"); + const argsPre = page.locator("pre", { hasText: '"prompt"' }).first(); + await expect(argsPre).toBeVisible(); + const subagentBtn = page.getByRole("button", { name: /子会话/ }).first(); + const cardFollowsArgs = await argsPre.evaluate( + (pre, card) => Boolean(pre.compareDocumentPosition(card) & Node.DOCUMENT_POSITION_FOLLOWING), + await subagentBtn.elementHandle(), + ); + expect(cardFollowsArgs, "subagent expansion renders below the tool arguments/output").toBe(true); + // --- Must still hold after reload (the fix's goal: parent Trace's child session pointer + server-side expansion) --- await page.reload(); await openSubagent(); diff --git a/packages/web/src/features/chat/tool-call-card.tsx b/packages/web/src/features/chat/tool-call-card.tsx index 0323d0d..9406375 100644 --- a/packages/web/src/features/chat/tool-call-card.tsx +++ b/packages/web/src/features/chat/tool-call-card.tsx @@ -1,7 +1,8 @@ /** * Tool call card: collapses to a single line by * default — status icon + tool name + duration (a live-ticking timer while running) + status - * badge; clicking expands full arguments, output, and any nested subagent. + * badge; clicking expands full arguments and output; a nested subagent renders below them + * regardless of collapsed state. * The pending-approval row is always visible regardless of collapsed state; when a pending * approval appears anywhere in a nested subagent chain, the card auto-expands once (respecting * the user's choice if they've manually collapsed it since). @@ -194,18 +195,6 @@ export function ToolCallCard({ item, ctx }: { item: ToolCallItem; ctx: StreamRen )} - {/* Subagent card: always visible (not hidden after completion, only collapsed by default), unaffected by the tool card's collapsed state. */} - {item.subagent && ( -
- -
- )} - {/* Expanded details: full arguments / output */} {open && (
@@ -238,6 +227,18 @@ export function ToolCallCard({ item, ctx }: { item: ToolCallItem; ctx: StreamRen )}
)} + + {/* Subagent card: always visible (not hidden after completion, only collapsed by default), unaffected by the tool card's collapsed state. Rendered below the expanded arguments/output so the nested conversation reads after the tool call's own content, not between the header and its details; pt-2 keeps a gap from the tinted args/output blocks above. */} + {item.subagent && ( +
+ +
+ )} ); }