groupSessionsByParent function

List<SessionGroup> groupSessionsByParent(
  1. List<SessionMetadata> sessions
)

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 [],
        ),
  ];
}