diff --git a/client/src/components/Chat/Messages/Content/Parts/SubagentCall.tsx b/client/src/components/Chat/Messages/Content/Parts/SubagentCall.tsx
index 7b2171e5a4..fa6ed549af 100644
--- a/client/src/components/Chat/Messages/Content/Parts/SubagentCall.tsx
+++ b/client/src/components/Chat/Messages/Content/Parts/SubagentCall.tsx
@@ -1,23 +1,26 @@
-import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
+import { useCallback, useEffect, useId, useMemo, useReducer, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
-import { ArrowDown, ChevronRight, Users } from 'lucide-react';
-import { OGDialog, OGDialogContent, OGDialogTitle, OGDialogDescription } from '@librechat/client';
import { ContentTypes, EModelEndpoint } from 'librechat-data-provider';
+import { ArrowDown, ChevronRight, Maximize2, Minimize2, Users } from 'lucide-react';
+import { OGDialog, OGDialogContent, OGDialogTitle, OGDialogDescription } from '@librechat/client';
+
import type { TAttachment, TMessage, TMessageContentParts } from 'librechat-data-provider';
-import ToolCall from '~/components/Chat/Messages/Content/ToolCall';
-import ToolCallGroup from '~/components/Chat/Messages/Content/ToolCallGroup';
-import Container from '~/components/Chat/Messages/Content/Container';
import type { PartWithIndex } from '~/components/Chat/Messages/Content/ParallelContent';
+import type { SubagentTickerLine } from '~/utils/subagentContent';
+
+import ToolCallGroup from '~/components/Chat/Messages/Content/ToolCallGroup';
+import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite';
+import { cn, groupSequentialToolCalls, parseToolName } from '~/utils';
+import Container from '~/components/Chat/Messages/Content/Container';
+import ToolCall from '~/components/Chat/Messages/Content/ToolCall';
+import { MessageContext } from '~/Providers/MessageContext';
+import { subagentProgressByToolCallId } from '~/store';
import MessageIcon from '~/components/Share/MessageIcon';
import { useAgentsMapContext } from '~/Providers';
-import Text from './Text';
-import Reasoning from './Reasoning';
import { AttachmentGroup } from './Attachment';
-import { MessageContext } from '~/Providers/MessageContext';
import { useLocalize } from '~/hooks';
-import { subagentProgressByToolCallId } from '~/store';
-import type { SubagentTickerLine } from '~/utils/subagentContent';
-import { cn, groupSequentialToolCalls, parseToolName } from '~/utils';
+import Reasoning from './Reasoning';
+import Text from './Text';
interface SubagentCallProps {
toolCallId: string;
@@ -163,6 +166,7 @@ export default function SubagentCall({
const progress = useRecoilValue(subagentProgressByToolCallId(toolCallId));
const agentsMap = useAgentsMapContext();
const [open, setOpen] = useState(false);
+ const [promptExpanded, setPromptExpanded] = useState(false);
const subagentType = progress?.subagentType ?? extractSubagentType(args);
const isSelfSpawn = subagentType === 'self';
@@ -241,18 +245,19 @@ export default function SubagentCall({
shouldThrottleTicker,
);
- const description = typeof args === 'string' ? tryDescription(args) : extractDescription(args);
+ const prompt = typeof args === 'string' ? tryPrompt(args) : extractPrompt(args);
/** Base verb-only label ("Running agent" / "Ran agent"). The agent name
* is rendered separately as a muted sub-label so "agent" stays a
* constant visual anchor regardless of name length. */
- const headerText = hasError
- ? localize('com_ui_subagent_errored')
- : cancelled
- ? localize('com_ui_subagent_cancelled')
- : running
- ? localize('com_ui_subagent_running')
- : localize('com_ui_subagent_complete');
+ let headerText = localize('com_ui_subagent_complete');
+ if (hasError) {
+ headerText = localize('com_ui_subagent_errored');
+ } else if (cancelled) {
+ headerText = localize('com_ui_subagent_cancelled');
+ } else if (running) {
+ headerText = localize('com_ui_subagent_running');
+ }
/** Muted sub-label shown to the right of the base label: the
* configured agent name for named subagents. Self-spawns omit it
* (redundant — the header already says "agent") as do cases where
@@ -333,14 +338,21 @@ export default function SubagentCall({
setIsAtBottom(distance <= DIALOG_AT_BOTTOM_THRESHOLD_PX);
}, []);
- /** Snap to bottom every time the dialog opens so a freshly-opened
- * dialog starts parked on the live cursor. */
+ /** Reset to the compact prompt preview every time the dialog closes. */
+ useEffect(() => {
+ if (open) return;
+ setPromptExpanded(false);
+ }, [open]);
+
+ /** Start at the top every time the dialog opens so the prompt reads as the
+ * first item in the scrollable trace instead of a fixed header. */
useEffect(() => {
if (!open) return;
const el = scrollRef.current;
if (!el) return;
- el.scrollTop = el.scrollHeight;
- setIsAtBottom(true);
+ el.scrollTop = 0;
+ const distance = el.scrollHeight - el.clientHeight;
+ setIsAtBottom(distance <= DIALOG_AT_BOTTOM_THRESHOLD_PX);
}, [open]);
/** Keep the view pinned to the bottom while the user is at/near it —
@@ -371,6 +383,61 @@ export default function SubagentCall({
setIsAtBottom(true);
}, []);
+ const emptyDialogText = running
+ ? localize('com_ui_subagent_no_result_yet')
+ : localize('com_ui_subagent_empty_result');
+
+ let dialogContent: JSX.Element;
+ if (contentParts.length > 0) {
+ dialogContent = (
+
+ {groupedParts.map((group) => {
+ if (group.type === 'single') {
+ const { part, idx } = group.part;
+ /** Per-type dispatch handles wrapping: TEXT goes through
+ * `Container`, THINK/TOOL_CALL render directly so their own
+ * wrappers set the width and spacing. */
+ return renderDialogPart(part, idx, idx === lastPartIndex);
+ }
+ /** Consecutive tool_calls (2+) collapse into a `Used N tools`
+ * group — same behavior as the main message view. */
+ return (
+ p.idx === lastPartIndex)}
+ renderPart={renderDialogPart}
+ lastContentIdx={lastPartIndex}
+ />
+ );
+ })}
+
+ );
+ } else if (output) {
+ /** Fallback: no aggregated content parts but the backend wrote a final
+ * tool_call output. Happens for older subagent runs recorded before the
+ * event forwarder existed. Route through the same leaf renderer so
+ * markdown renders properly. */
+ dialogContent = (
+
+
+
+ );
+ } else {
+ dialogContent =
{emptyDialogText}
;
+ }
+
return (
<>