File size: 2,379 Bytes
5710dd0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import type { AgentInfo } from './agent-record-types';

export interface AgentNode extends AgentInfo {
  children: AgentNode[];
}

/**
 * Build a parent/child tree from the flat agent inventory found on
 * `state.json.agents`. Roots are agents with no `parentAgentId`, plus any
 * agent whose `parentAgentId` does not resolve in the inventory (orphans).
 * The returned roots are sorted so that the `main` agent always appears
 * first; remaining agents fall back to a numeric `agent-N` order (so
 * `agent-2` precedes `agent-10`), then a stable lexicographic order. The
 * same ordering is applied to each node's children.
 */
export function buildAgentTree(agents: ReadonlyArray<AgentInfo>): AgentNode[] {
  const byId = new Map<string, AgentNode>();
  for (const a of agents) byId.set(a.agentId, { ...a, children: [] });

  const roots: AgentNode[] = [];
  for (const node of byId.values()) {
    if (node.parentAgentId !== null && byId.has(node.parentAgentId)) {
      byId.get(node.parentAgentId)!.children.push(node);
    } else {
      roots.push(node);
    }
  }
  for (const node of byId.values()) {
    node.children.sort(sortAgents);
  }
  return roots.sort(sortAgents);
}

function sortAgents(a: AgentNode, b: AgentNode): number {
  return compareAgentIds(a.agentId, b.agentId);
}

/**
 * Shared agent-id ordering: `main` always first, then `agent-N` records by
 * numeric suffix (so `agent-2` precedes `agent-10`), with a lexicographic
 * fallback for any id that does not match the `agent-N` shape.
 *
 * In practice a sibling set is `main` plus `agent-N` ids (the engine's id
 * generator only emits those). The `na`/`nb`-only branches below exist solely
 * to keep a stable TOTAL order when foreign/hand-edited ids (reachable via
 * `state.json` keys or `discoverAgentsFromDisk` directory names) are mixed in:
 * all `agent-N` ids sort before any non-`agent-N` id, so the comparator stays
 * transitive instead of degenerating into V8's order-dependent output.
 */
export function compareAgentIds(a: string, b: string): number {
  if (a === b) return 0;
  if (a === 'main') return -1;
  if (b === 'main') return 1;
  const na = /^agent-(\d+)$/.exec(a);
  const nb = /^agent-(\d+)$/.exec(b);
  if (na && nb) return Number(na[1]) - Number(nb[1]);
  if (na) return -1; // all agent-N sort before any non-agent-N id
  if (nb) return 1;
  return a.localeCompare(b);
}