π File detail
utils/collapseHookSummaries.ts
π§© .tsπ 60 linesπΎ 1,714 bytesπ text
β Back to All Filesπ― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes collapseHookSummaries β mainly functions, hooks, or classes. It composes internal code from types (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import type { RenderableMessage, SystemStopHookSummaryMessage, } from '../types/message.js'
π€ Exports (heuristic)
collapseHookSummaries
π₯οΈ Source preview
import type {
RenderableMessage,
SystemStopHookSummaryMessage,
} from '../types/message.js'
function isLabeledHookSummary(
msg: RenderableMessage,
): msg is SystemStopHookSummaryMessage {
return (
msg.type === 'system' &&
msg.subtype === 'stop_hook_summary' &&
msg.hookLabel !== undefined
)
}
/**
* Collapses consecutive hook summary messages with the same hookLabel
* (e.g. PostToolUse) into a single summary. This happens when parallel
* tool calls each emit their own hook summary.
*/
export function collapseHookSummaries(
messages: RenderableMessage[],
): RenderableMessage[] {
const result: RenderableMessage[] = []
let i = 0
while (i < messages.length) {
const msg = messages[i]!
if (isLabeledHookSummary(msg)) {
const label = msg.hookLabel
const group: SystemStopHookSummaryMessage[] = []
while (i < messages.length) {
const next = messages[i]!
if (!isLabeledHookSummary(next) || next.hookLabel !== label) break
group.push(next)
i++
}
if (group.length === 1) {
result.push(msg)
} else {
result.push({
...msg,
hookCount: group.reduce((sum, m) => sum + m.hookCount, 0),
hookInfos: group.flatMap(m => m.hookInfos),
hookErrors: group.flatMap(m => m.hookErrors),
preventedContinuation: group.some(m => m.preventedContinuation),
hasOutput: group.some(m => m.hasOutput),
// Parallel tool calls' hooks overlap; max is closest to wall-clock.
totalDurationMs: Math.max(...group.map(m => m.totalDurationMs ?? 0)),
})
}
} else {
result.push(msg)
i++
}
}
return result
}