mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-30 19:32:46 +08:00
Backfill the subagent display name from the second start event
The dispatch-time subagent_start fires before the trigger args (and therefore the name parameter) have streamed; the phase-3 start re-announces the lane with the name. The block builder was dropping that duplicate wholesale, losing the name on streaming providers — now it backfills subagentName onto the existing block instead. (The home turn-model path already reconciled this case.)
This commit is contained in:
@@ -936,4 +936,70 @@ describe('copilot go stream helpers', () => {
|
||||
expect(subagentBlock?.parentSpanId).toBe('S1')
|
||||
expect(subagentBlock?.parentToolCallId).toBe('tc-deploy-inner')
|
||||
})
|
||||
|
||||
it('backfills the display name when only the second subagent start carries it', async () => {
|
||||
const scope = {
|
||||
lane: 'subagent' as const,
|
||||
agentId: 'research',
|
||||
parentToolCallId: 'tc-research',
|
||||
spanId: 'S3',
|
||||
parentSpanId: 'S1',
|
||||
}
|
||||
vi.mocked(fetch).mockResolvedValueOnce(
|
||||
createSseResponse([
|
||||
// Dispatch-time start: fires before the trigger args stream, so no name.
|
||||
createEvent({
|
||||
streamId: 'stream-1',
|
||||
cursor: '1',
|
||||
seq: 1,
|
||||
requestId: 'req-1',
|
||||
type: MothershipStreamV1EventType.span,
|
||||
scope,
|
||||
payload: {
|
||||
kind: 'subagent',
|
||||
event: 'start',
|
||||
agent: 'research',
|
||||
data: { tool_call_id: 'tc-research' },
|
||||
},
|
||||
}),
|
||||
// Phase-3 start re-announces the lane WITH the orchestrator-chosen name.
|
||||
createEvent({
|
||||
streamId: 'stream-1',
|
||||
cursor: '2',
|
||||
seq: 2,
|
||||
requestId: 'req-1',
|
||||
type: MothershipStreamV1EventType.span,
|
||||
scope,
|
||||
payload: {
|
||||
kind: 'subagent',
|
||||
event: 'start',
|
||||
agent: 'research',
|
||||
data: { tool_call_id: 'tc-research', name: 'Pricing research' },
|
||||
},
|
||||
}),
|
||||
createEvent({
|
||||
streamId: 'stream-1',
|
||||
cursor: '3',
|
||||
seq: 3,
|
||||
requestId: 'req-1',
|
||||
type: MothershipStreamV1EventType.complete,
|
||||
payload: { status: MothershipStreamV1CompletionStatus.complete },
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
const context = createStreamingContext()
|
||||
const execContext: ExecutionContext = {
|
||||
userId: 'user-1',
|
||||
workflowId: 'workflow-1',
|
||||
}
|
||||
|
||||
await runStreamLoop('https://example.com/mothership/stream', {}, context, execContext, {
|
||||
timeout: 1000,
|
||||
})
|
||||
|
||||
const subagentBlocks = context.contentBlocks.filter((block) => block.type === 'subagent')
|
||||
expect(subagentBlocks).toHaveLength(1)
|
||||
expect(subagentBlocks[0]?.subagentName).toBe('Pricing research')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -433,25 +433,36 @@ export async function runStreamLoop(
|
||||
context.subAgentToolCalls[toolCallId] ??= []
|
||||
}
|
||||
if (toolCallId && subagentName) {
|
||||
const payloadData = streamEvent.payload.data
|
||||
const rawName =
|
||||
payloadData && typeof payloadData === 'object' && !Array.isArray(payloadData)
|
||||
? (payloadData as Record<string, unknown>).name
|
||||
: undefined
|
||||
const displayName = typeof rawName === 'string' && rawName ? rawName : undefined
|
||||
const openParents = (context.openSubagentParents ??= new Set<string>())
|
||||
if (!openParents.has(toolCallId)) {
|
||||
openParents.add(toolCallId)
|
||||
const payloadData = streamEvent.payload.data
|
||||
const displayName =
|
||||
payloadData && typeof payloadData === 'object' && !Array.isArray(payloadData)
|
||||
? (payloadData as Record<string, unknown>).name
|
||||
: undefined
|
||||
context.contentBlocks.push({
|
||||
type: 'subagent',
|
||||
content: subagentName,
|
||||
...(typeof displayName === 'string' && displayName
|
||||
? { subagentName: displayName }
|
||||
: {}),
|
||||
...(displayName ? { subagentName: displayName } : {}),
|
||||
parentToolCallId: toolCallId,
|
||||
...(spanId ? { spanId } : {}),
|
||||
...(parentSpanId ? { parentSpanId } : {}),
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
} else if (displayName) {
|
||||
// The lane was opened by the dispatch-time start, which fires
|
||||
// before the trigger args (and therefore the name) exist. The
|
||||
// phase-3 start re-announces the lane WITH the name; backfill
|
||||
// it instead of dropping the duplicate wholesale.
|
||||
for (let i = context.contentBlocks.length - 1; i >= 0; i--) {
|
||||
const b = context.contentBlocks[i]
|
||||
if (b.type === 'subagent' && b.parentToolCallId === toolCallId) {
|
||||
if (!b.subagentName) b.subagentName = displayName
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn('subagent start missing toolCallId or agent name', {
|
||||
|
||||
Reference in New Issue
Block a user