mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(cli,web): group orphaned subagent trace by parentToolUseId (#1175)
This commit is contained in:
@@ -29,6 +29,11 @@ const RawJSONLinesBaseSchema = z.object({
|
||||
uuid: z.string().optional(),
|
||||
parentUuid: z.string().nullable().optional(),
|
||||
isSidechain: z.boolean().optional(),
|
||||
// The tool_use id of the Agent/Task tool_use that spawned this sidechain
|
||||
// message, when present. Preserved (not just consumed) so downstream (web
|
||||
// tracer) can group sidechain messages directly by this id rather than
|
||||
// solely by exact-matching a sidechain root's prompt text.
|
||||
parentToolUseId: z.string().optional(),
|
||||
isMeta: z.boolean().optional(),
|
||||
isCompactSummary: z.boolean().optional(),
|
||||
userType: z.string().optional(),
|
||||
|
||||
@@ -1041,6 +1041,63 @@ describe('SDKToLogConverter', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sidechain parentToolUseId preservation (subagent trace grouping fix)', () => {
|
||||
it('preserves parent_tool_use_id as parentToolUseId on sidechain user messages', () => {
|
||||
const sdkMessage = {
|
||||
type: 'user',
|
||||
parent_tool_use_id: 'toolu_abc123',
|
||||
message: { role: 'user', content: 'sidechain prompt' }
|
||||
} as unknown as SDKUserMessage
|
||||
|
||||
const logMessage = converter.convert(sdkMessage) as any
|
||||
|
||||
expect(logMessage?.isSidechain).toBe(true)
|
||||
expect(logMessage?.parentToolUseId).toBe('toolu_abc123')
|
||||
})
|
||||
|
||||
it('preserves parent_tool_use_id on sidechain assistant messages (subagent turns)', () => {
|
||||
const sdkMessage = {
|
||||
type: 'assistant',
|
||||
parent_tool_use_id: 'toolu_abc123',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: [{ type: 'text', text: 'subagent reply' }]
|
||||
}
|
||||
} as unknown as SDKAssistantMessage
|
||||
|
||||
const logMessage = converter.convert(sdkMessage) as any
|
||||
|
||||
expect(logMessage?.isSidechain).toBe(true)
|
||||
expect(logMessage?.parentToolUseId).toBe('toolu_abc123')
|
||||
})
|
||||
|
||||
it('does not set parentToolUseId on non-sidechain (top-level) messages', () => {
|
||||
const sdkMessage: SDKUserMessage = {
|
||||
type: 'user',
|
||||
message: { role: 'user', content: 'top-level message' }
|
||||
}
|
||||
|
||||
const logMessage = converter.convert(sdkMessage) as any
|
||||
|
||||
expect(logMessage?.isSidechain).toBe(false)
|
||||
expect(logMessage?.parentToolUseId).toBeUndefined()
|
||||
})
|
||||
|
||||
it('preserves parentToolUseId on interrupted sidechain tool results', () => {
|
||||
const logMessage = converter.generateInterruptedToolResult('toolu_child', 'toolu_parent') as any
|
||||
|
||||
expect(logMessage?.isSidechain).toBe(true)
|
||||
expect(logMessage?.parentToolUseId).toBe('toolu_parent')
|
||||
})
|
||||
|
||||
it('does not set parentToolUseId on interrupted top-level tool results', () => {
|
||||
const logMessage = converter.generateInterruptedToolResult('toolu_child') as any
|
||||
|
||||
expect(logMessage?.isSidechain).toBe(false)
|
||||
expect(logMessage?.parentToolUseId).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Convenience function', () => {
|
||||
it('should convert single message without state', () => {
|
||||
const sdkMessage: SDKUserMessage = {
|
||||
|
||||
@@ -235,14 +235,22 @@ export class SDKToLogConverter {
|
||||
const timestamp = new Date().toISOString()
|
||||
let parentUuid = this.lastUuid;
|
||||
let isSidechain = false;
|
||||
// Preserved (not just consumed) so the web tracer can group sidechain
|
||||
// messages directly by the spawning Agent tool_use id, instead of relying
|
||||
// solely on the SDK emitting a prompt-holding sidechain root to exact-match
|
||||
// against. Some subagents (e.g. background/task_started) never emit that
|
||||
// root, orphaning every child that only carries this id.
|
||||
let parentToolUseId: string | undefined;
|
||||
if (sdkMessage.parent_tool_use_id) {
|
||||
isSidechain = true;
|
||||
parentToolUseId = (sdkMessage as any).parent_tool_use_id;
|
||||
parentUuid = this.sidechainLastUUID.get((sdkMessage as any).parent_tool_use_id) ?? null;
|
||||
this.sidechainLastUUID.set((sdkMessage as any).parent_tool_use_id!, uuid);
|
||||
}
|
||||
const baseFields = {
|
||||
parentUuid: parentUuid,
|
||||
isSidechain: isSidechain,
|
||||
parentToolUseId,
|
||||
userType: 'external' as const,
|
||||
cwd: this.context.cwd,
|
||||
sessionId: this.context.sessionId,
|
||||
@@ -506,6 +514,7 @@ export class SDKToLogConverter {
|
||||
const logMessage: RawJSONLines = {
|
||||
type: 'user',
|
||||
isSidechain: isSidechain,
|
||||
parentToolUseId: parentToolUseId ?? undefined,
|
||||
uuid,
|
||||
message: {
|
||||
role: 'user',
|
||||
|
||||
Reference in New Issue
Block a user