Skip to content

Commit 752ddd2

Browse files
committed
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.)
1 parent ba78829 commit 752ddd2

2 files changed

Lines changed: 85 additions & 8 deletions

File tree

apps/sim/lib/copilot/request/go/stream.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,4 +936,70 @@ describe('copilot go stream helpers', () => {
936936
expect(subagentBlock?.parentSpanId).toBe('S1')
937937
expect(subagentBlock?.parentToolCallId).toBe('tc-deploy-inner')
938938
})
939+
940+
it('backfills the display name when only the second subagent start carries it', async () => {
941+
const scope = {
942+
lane: 'subagent' as const,
943+
agentId: 'research',
944+
parentToolCallId: 'tc-research',
945+
spanId: 'S3',
946+
parentSpanId: 'S1',
947+
}
948+
vi.mocked(fetch).mockResolvedValueOnce(
949+
createSseResponse([
950+
// Dispatch-time start: fires before the trigger args stream, so no name.
951+
createEvent({
952+
streamId: 'stream-1',
953+
cursor: '1',
954+
seq: 1,
955+
requestId: 'req-1',
956+
type: MothershipStreamV1EventType.span,
957+
scope,
958+
payload: {
959+
kind: 'subagent',
960+
event: 'start',
961+
agent: 'research',
962+
data: { tool_call_id: 'tc-research' },
963+
},
964+
}),
965+
// Phase-3 start re-announces the lane WITH the orchestrator-chosen name.
966+
createEvent({
967+
streamId: 'stream-1',
968+
cursor: '2',
969+
seq: 2,
970+
requestId: 'req-1',
971+
type: MothershipStreamV1EventType.span,
972+
scope,
973+
payload: {
974+
kind: 'subagent',
975+
event: 'start',
976+
agent: 'research',
977+
data: { tool_call_id: 'tc-research', name: 'Pricing research' },
978+
},
979+
}),
980+
createEvent({
981+
streamId: 'stream-1',
982+
cursor: '3',
983+
seq: 3,
984+
requestId: 'req-1',
985+
type: MothershipStreamV1EventType.complete,
986+
payload: { status: MothershipStreamV1CompletionStatus.complete },
987+
}),
988+
])
989+
)
990+
991+
const context = createStreamingContext()
992+
const execContext: ExecutionContext = {
993+
userId: 'user-1',
994+
workflowId: 'workflow-1',
995+
}
996+
997+
await runStreamLoop('https://example.com/mothership/stream', {}, context, execContext, {
998+
timeout: 1000,
999+
})
1000+
1001+
const subagentBlocks = context.contentBlocks.filter((block) => block.type === 'subagent')
1002+
expect(subagentBlocks).toHaveLength(1)
1003+
expect(subagentBlocks[0]?.subagentName).toBe('Pricing research')
1004+
})
9391005
})

apps/sim/lib/copilot/request/go/stream.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -433,25 +433,36 @@ export async function runStreamLoop(
433433
context.subAgentToolCalls[toolCallId] ??= []
434434
}
435435
if (toolCallId && subagentName) {
436+
const payloadData = streamEvent.payload.data
437+
const rawName =
438+
payloadData && typeof payloadData === 'object' && !Array.isArray(payloadData)
439+
? (payloadData as Record<string, unknown>).name
440+
: undefined
441+
const displayName = typeof rawName === 'string' && rawName ? rawName : undefined
436442
const openParents = (context.openSubagentParents ??= new Set<string>())
437443
if (!openParents.has(toolCallId)) {
438444
openParents.add(toolCallId)
439-
const payloadData = streamEvent.payload.data
440-
const displayName =
441-
payloadData && typeof payloadData === 'object' && !Array.isArray(payloadData)
442-
? (payloadData as Record<string, unknown>).name
443-
: undefined
444445
context.contentBlocks.push({
445446
type: 'subagent',
446447
content: subagentName,
447-
...(typeof displayName === 'string' && displayName
448-
? { subagentName: displayName }
449-
: {}),
448+
...(displayName ? { subagentName: displayName } : {}),
450449
parentToolCallId: toolCallId,
451450
...(spanId ? { spanId } : {}),
452451
...(parentSpanId ? { parentSpanId } : {}),
453452
timestamp: Date.now(),
454453
})
454+
} else if (displayName) {
455+
// The lane was opened by the dispatch-time start, which fires
456+
// before the trigger args (and therefore the name) exist. The
457+
// phase-3 start re-announces the lane WITH the name; backfill
458+
// it instead of dropping the duplicate wholesale.
459+
for (let i = context.contentBlocks.length - 1; i >= 0; i--) {
460+
const b = context.contentBlocks[i]
461+
if (b.type === 'subagent' && b.parentToolCallId === toolCallId) {
462+
if (!b.subagentName) b.subagentName = displayName
463+
break
464+
}
465+
}
455466
}
456467
} else {
457468
logger.warn('subagent start missing toolCallId or agent name', {

0 commit comments

Comments
 (0)