Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,950 changes: 6,239 additions & 711 deletions apps/docs/content/docs/en/integrations/sportmonks.mdx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ interface AgentGroupProps {
items: AgentGroupItem[]
isDelegating?: boolean
isStreaming?: boolean
defaultExpanded?: boolean
/** This group is the latest section in its parent sequence (drives collapse). */
isCurrentSection?: boolean
/** The subagent lane is still open (no subagent_end yet) — i.e. actively running. */
isLaneOpen?: boolean
}

export function isAgentGroupResolved(items: AgentGroupItem[]): boolean {
Expand All @@ -55,7 +58,8 @@ export function AgentGroup({
items,
isDelegating = false,
isStreaming = false,
defaultExpanded = false,
isCurrentSection = false,
isLaneOpen = false,
}: AgentGroupProps) {
const AgentIcon = getAgentIcon(agentName)
const hasItems = items.length > 0
Expand All @@ -66,11 +70,17 @@ export function AgentGroup({
// transport gating is needed to stop an aborted-before-first-tool spinner.
const showDelegatingSpinner = isDelegating && !resolved

// Expand only while the turn is live and the group is still open or working.
// Once the turn ends (isStreaming false) — or a subagent closes mid-turn — the
// group auto-collapses, so finished subagent blocks never stay expanded. A
// manual toggle pins the choice for the rest of the message.
const autoExpanded = isStreaming && (defaultExpanded || !resolved)
// Expand while the turn is live and any of: the lane is open (the subagent is
// actively running), this is the current/latest section, or there is unresolved
// work. A finished group stays open until the NEXT section starts (it is no
// longer the latest), instead of collapsing the instant its own work resolves.
// Keying "still running" off the lane-open signal (not `resolved` alone) avoids
// a collapse/reopen flicker on parallel siblings: a subagent's tools all
// momentarily read "done" in the gap between its last search and its `respond`
// ("Gathering thoughts") tool, transiently flipping `resolved` true; the open
// lane bridges that gap so the row never collapses mid-run. The turn ending
// (isStreaming false) collapses everything; a manual toggle pins the choice.
const autoExpanded = isStreaming && (isCurrentSection || isLaneOpen || !resolved)
const [manualExpanded, setManualExpanded] = useState<boolean | null>(null)
const expanded = manualExpanded ?? autoExpanded

Expand Down Expand Up @@ -135,7 +145,8 @@ export function AgentGroup({
items={item.group.items}
isDelegating={item.group.isDelegating}
isStreaming={isStreaming}
defaultExpanded={item.group.isOpen}
isCurrentSection={idx === items.length - 1}
isLaneOpen={item.group.isOpen}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ export { AgentGroup, CircleStop, isAgentGroupResolved } from './agent-group'
export { ChatContent } from './chat-content'
export { Options } from './options'
export { PendingTagIndicator, parseSpecialTags, SpecialTags } from './special-tags'
export { ThinkingBlock } from './thinking-block'

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,7 @@ import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/ch
import type { ContentBlock, OptionItem, ToolCallData } from '../../types'
import { SUBAGENT_LABELS } from '../../types'
import type { AgentGroupItem } from './components'
import {
AgentGroup,
ChatContent,
CircleStop,
Options,
PendingTagIndicator,
ThinkingBlock,
} from './components'
import { AgentGroup, ChatContent, CircleStop, Options, PendingTagIndicator } from './components'
import { deriveMessagePhase, isToolDone, type MessagePhase } from './utils'

const FILE_SUBAGENT_ID = 'file'
Expand All @@ -29,14 +22,6 @@ interface TextSegment {
content: string
}

interface ThinkingSegment {
type: 'thinking'
id: string
content: string
startedAt?: number
endedAt?: number
}

interface AgentGroupSegment {
type: 'agent_group'
id: string
Expand All @@ -56,12 +41,7 @@ interface StoppedSegment {
type: 'stopped'
}

type MessageSegment =
| TextSegment
| ThinkingSegment
| AgentGroupSegment
| OptionsSegment
| StoppedSegment
type MessageSegment = TextSegment | AgentGroupSegment | OptionsSegment | StoppedSegment

const SUBAGENT_KEYS = new Set(Object.keys(SUBAGENT_LABELS))

Expand Down Expand Up @@ -279,23 +259,9 @@ function parseBlocksWithSpanTree(blocks: ContentBlock[]): MessageSegment[] {
continue
}

if (block.type === 'thinking') {
if (!block.content?.trim()) continue
const last = segments[segments.length - 1]
if (last?.type === 'thinking' && last.endedAt === undefined) {
last.content += block.content
if (block.endedAt !== undefined) last.endedAt = block.endedAt
} else {
segments.push({
type: 'thinking',
id: `thinking-${i}`,
content: block.content,
startedAt: block.timestamp,
endedAt: block.endedAt,
})
}
continue
}
// Main-agent thinking is intentionally not rendered. The reasoning is still
// reduced and persisted upstream — this is a display-only omission.
if (block.type === 'thinking') continue

if (block.type === 'text') {
if (!block.content) continue
Expand Down Expand Up @@ -515,21 +481,10 @@ function parseBlocksLegacy(blocks: ContentBlock[]): MessageSegment[] {
}

if (block.type === 'thinking') {
// Main-agent thinking is not rendered, but it still breaks open subagent
// lanes so later chunks don't merge across it (display-only omission).
if (!block.content?.trim()) continue
flushLanes()
const last = segments[segments.length - 1]
if (last?.type === 'thinking' && last.endedAt === undefined) {
last.content += block.content
if (block.endedAt !== undefined) last.endedAt = block.endedAt
} else {
segments.push({
type: 'thinking',
id: `thinking-${i}`,
content: block.content,
startedAt: block.timestamp,
endedAt: block.endedAt,
})
}
continue
}

Expand Down Expand Up @@ -776,29 +731,6 @@ function MessageContentInner({
}
/>
)
case 'thinking': {
const isActive =
isStreaming && i === segments.length - 1 && segment.endedAt === undefined
const elapsedMs =
segment.startedAt !== undefined && segment.endedAt !== undefined
? segment.endedAt - segment.startedAt
: undefined
// Hide completed thinking that took 3s or less — quick thinking
// isn't worth the visual noise. Still show while active (unknown
// duration yet) and still show when timing is missing (old
// persisted blocks) so we don't drop historical content.
if (elapsedMs !== undefined && elapsedMs <= 3000) return null
return (
<div key={segment.id} className={isStreaming ? 'animate-stream-fade-in' : undefined}>
<ThinkingBlock
content={segment.content}
isActive={isActive}
startedAt={segment.startedAt}
isStreaming={isStreaming}
/>
</div>
)
}
case 'agent_group': {
return (
<div key={segment.id} className={isStreaming ? 'animate-stream-fade-in' : undefined}>
Expand All @@ -809,7 +741,8 @@ function MessageContentInner({
items={segment.items}
isDelegating={segment.isDelegating}
isStreaming={isStreaming}
defaultExpanded={segment.isOpen}
isCurrentSection={i === segments.length - 1}
isLaneOpen={segment.isOpen}
/>
</div>
)
Expand Down
Loading
Loading