File size: 3,905 Bytes
7c5ed63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import { describe, it, expect } from 'vitest';
import { buildAgentTree, compareAgentIds } from '../../src/lib/agent-tree';
import type { AgentInfo } from '../../src/lib/agent-record-types';

function info(overrides: Partial<AgentInfo> & Pick<AgentInfo, 'agentId'>): AgentInfo {
  return {
    type: 'sub',
    parentAgentId: null,
    profileName: null,
    homedir: `/tmp/${overrides.agentId}`,
    wireExists: true,
    wireRecordCount: 0,
    wireProtocolVersion: '1.1',
    swarmItem: null,
    ...overrides,
  };
}

describe('agent-tree', () => {
  it('returns single main agent as the only root', () => {
    const tree = buildAgentTree([info({ agentId: 'main', type: 'main' })]);
    expect(tree).toHaveLength(1);
    expect(tree[0]!.agentId).toBe('main');
    expect(tree[0]!.children).toEqual([]);
  });

  it('attaches a sub agent to its main parent', () => {
    const tree = buildAgentTree([
      info({ agentId: 'main', type: 'main' }),
      info({ agentId: 'agent-0', type: 'sub', parentAgentId: 'main' }),
    ]);
    expect(tree).toHaveLength(1);
    expect(tree[0]!.agentId).toBe('main');
    expect(tree[0]!.children).toHaveLength(1);
    expect(tree[0]!.children[0]!.agentId).toBe('agent-0');
    expect(tree[0]!.children[0]!.parentAgentId).toBe('main');
  });

  it('treats orphan parentAgentId as a root node', () => {
    const tree = buildAgentTree([
      info({ agentId: 'main', type: 'main' }),
      info({ agentId: 'agent-0', type: 'sub', parentAgentId: 'does-not-exist' }),
    ]);
    expect(tree).toHaveLength(2);
    const ids = tree.map((n) => n.agentId).sort();
    expect(ids).toEqual(['agent-0', 'main']);
    // orphan is still a root, no children attached anywhere
    const orphan = tree.find((n) => n.agentId === 'agent-0')!;
    expect(orphan.children).toEqual([]);
  });

  it('sorts main as the first root regardless of input order', () => {
    const tree = buildAgentTree([
      info({ agentId: 'agent-1', type: 'sub', parentAgentId: 'orphan' }),
      info({ agentId: 'main', type: 'main' }),
      info({ agentId: 'agent-2', type: 'sub', parentAgentId: 'orphan' }),
    ]);
    expect(tree[0]!.agentId).toBe('main');
  });

  it('orders agents by numeric suffix, main first (agent-2 before agent-10)', () => {
    const mk = (id: string): AgentInfo => ({
      agentId: id, type: id === 'main' ? 'main' : 'sub', parentAgentId: id === 'main' ? null : 'main',
      profileName: null, homedir: '', wireExists: true, wireRecordCount: 0, wireProtocolVersion: null, swarmItem: null,
    });
    const tree = buildAgentTree([mk('main'), mk('agent-10'), mk('agent-2')]);
    const order = [tree[0]!.agentId, ...tree[0]!.children.map((c) => c.agentId)];
    expect(order).toEqual(['main', 'agent-2', 'agent-10']);
  });

  it('orders orphan ROOTS by numeric suffix (agent-2 before agent-10)', () => {
    const tree = buildAgentTree([
      info({ agentId: 'agent-10', type: 'sub', parentAgentId: 'missing' }),
      info({ agentId: 'agent-2', type: 'sub', parentAgentId: 'missing' }),
    ]);
    expect(tree.map((n) => n.agentId)).toEqual(['agent-2', 'agent-10']);
  });

  it('compareAgentIds is a deterministic total order when agent-N and foreign ids mix', () => {
    // 'agent-1a' is a foreign/hand-edited id reachable via state.json keys or
    // discoverAgentsFromDisk directory names — it does not match agent-N.
    // Under the new rule: all agent-N ids sort numerically first, then any
    // non-agent-N id by localeCompare. Sorting any permutation must yield the
    // same order; the OLD comparator was intransitive and order-dependent here.
    const forward = ['agent-2', 'agent-1a', 'agent-10'];
    const reverse = [...forward].reverse();
    const expected = ['agent-2', 'agent-10', 'agent-1a'];
    expect([...forward].sort(compareAgentIds)).toEqual(expected);
    expect([...reverse].sort(compareAgentIds)).toEqual(expected);
  });
});