groupSessionsByParent function
Groups sessions into SessionGroups: every subagent session whose
parent is present in the list nests under it; orphans stay top-level.
Pure projection over header metadata — no I/O (AC5). Order within each level follows the input order, so callers keep their own sort: the CLI passes current-folder-first activity order, the app its stable creation order.
Depth is capped at one: a subagent can never spawn a subagent, so a child pointing at another child degrades to an orphan instead of nesting deeper.
Implementation
List<SessionGroup> groupSessionsByParent(List<SessionMetadata> sessions) {
final byId = {for (final s in sessions) s.id: s};
final childrenByParent = <String, List<SessionMetadata>>{};
final orphans = <SessionMetadata>{};
for (final session in sessions) {
if (!isSubagentSession(session)) continue;
final parentId = subagentParentId(session);
final parent = parentId == null ? null : byId[parentId];
if (parent == null || isSubagentSession(parent)) {
// Empty/missing parent id, parent absent from this list, or the
// parent is itself a child (depth must never exceed one): render
// top-level with a subagent marker — a subagent row can never
// vanish from a tree listing.
orphans.add(session);
} else {
childrenByParent.putIfAbsent(parentId!, () => []).add(session);
}
}
return [
// Top level follows the input order (the caller's activity sort), so
// orphans sit at their own activity position instead of being dumped
// after the mains.
for (final session in sessions)
if (!isSubagentSession(session) || orphans.contains(session))
SessionGroup(
main: session,
children: childrenByParent[session.id] ?? const [],
),
];
}