tycono 0.1.96-beta.29 → 0.1.96-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.1.96-beta.29",
3
+ "version": "0.1.96-beta.30",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/tui/api.ts CHANGED
@@ -83,6 +83,7 @@ export interface SessionInfo {
83
83
  status: string;
84
84
  source: string;
85
85
  waveId?: string;
86
+ parentSessionId?: string;
86
87
  createdAt: string;
87
88
  }
88
89
 
package/src/tui/app.tsx CHANGED
@@ -29,76 +29,117 @@ type View = 'loading' | 'setup' | 'dashboard';
29
29
 
30
30
  let sysLineId = 100000;
31
31
 
32
- /** Format agent tree for /agents command */
32
+ /** Format agent tree: Wave Role → Session + resources */
33
33
  function formatAgentsTree(
34
34
  waves: WaveInfo[],
35
+ allSessions: import('./api').SessionInfo[],
35
36
  activeSessions: ActiveSessionInfo[],
36
37
  focusedWaveId: string | null,
37
38
  ): StreamLine[] {
38
39
  const lines: StreamLine[] = [];
39
40
 
40
- if (waves.length === 0 && activeSessions.length === 0) {
41
- lines.push({ id: ++sysLineId, text: 'No active agents.', color: 'gray' });
42
- return lines;
41
+ // Build port lookup: sessionId ActiveSessionInfo
42
+ const portMap = new Map<string, ActiveSessionInfo>();
43
+ for (const s of activeSessions) {
44
+ portMap.set(s.sessionId, s);
43
45
  }
44
46
 
45
47
  // Group sessions by waveId
46
- const sessionsByWave = new Map<string, ActiveSessionInfo[]>();
47
- const unlinked: ActiveSessionInfo[] = [];
48
- for (const s of activeSessions) {
48
+ const sessionsByWave = new Map<string, import('./api').SessionInfo[]>();
49
+ const unlinked: import('./api').SessionInfo[] = [];
50
+
51
+ for (const s of allSessions) {
49
52
  if (s.waveId) {
50
53
  if (!sessionsByWave.has(s.waveId)) sessionsByWave.set(s.waveId, []);
51
54
  sessionsByWave.get(s.waveId)!.push(s);
52
- } else {
55
+ } else if (s.status === 'active') {
53
56
  unlinked.push(s);
54
57
  }
55
58
  }
56
59
 
57
- // Display each wave
60
+ if (waves.length === 0 && unlinked.length === 0) {
61
+ lines.push({ id: ++sysLineId, text: 'No active agents.', color: 'gray' });
62
+ return lines;
63
+ }
64
+
65
+ // Display each wave as tree
58
66
  for (let i = 0; i < waves.length; i++) {
59
67
  const w = waves[i];
60
68
  const isFocused = w.waveId === focusedWaveId;
61
- const marker = isFocused ? '*' : ' ';
69
+ const marker = isFocused ? '\u25B8' : ' ';
62
70
  const label = w.directive ? w.directive.slice(0, 50) : '(idle)';
71
+ const waveSessions = sessionsByWave.get(w.waveId) ?? [];
72
+
63
73
  lines.push({
64
74
  id: ++sysLineId,
65
- text: `${marker}Wave ${i + 1}: "${label}"`,
75
+ text: `${marker}Wave ${i + 1}: ${label} (${waveSessions.length} sessions)`,
66
76
  color: isFocused ? 'green' : 'cyan',
67
77
  });
68
78
 
69
- const waveSessions = sessionsByWave.get(w.waveId) ?? [];
70
79
  if (waveSessions.length === 0) {
71
- lines.push({ id: ++sysLineId, text: ' (no agents)', color: 'gray' });
80
+ lines.push({ id: ++sysLineId, text: ' (empty)', color: 'gray' });
81
+ continue;
72
82
  }
83
+
84
+ // Group wave sessions by role
85
+ const roleGroups = new Map<string, import('./api').SessionInfo[]>();
73
86
  for (const s of waveSessions) {
74
- const statusIcon = s.status === 'active' ? '\u25CF' : s.status === 'dead' ? '\u25CF' : '\u25CB';
75
- const statusColor = s.status === 'active' ? 'green' : s.status === 'dead' ? 'red' : 'gray';
76
- const portInfo = s.ports.api ? `API:${s.ports.api} Vite:${s.ports.vite}` : '(no ports)';
77
- const worktree = s.worktreePath ? `\u{1F33F} ${s.worktreePath.split('/').pop()}` : '';
87
+ if (!roleGroups.has(s.roleId)) roleGroups.set(s.roleId, []);
88
+ roleGroups.get(s.roleId)!.push(s);
89
+ }
90
+
91
+ const roleEntries = Array.from(roleGroups.entries());
92
+ for (let ri = 0; ri < roleEntries.length; ri++) {
93
+ const [roleId, roleSessions] = roleEntries[ri];
94
+ const isLastRole = ri === roleEntries.length - 1;
95
+ const branch = isLastRole ? '\u2514\u2500' : '\u251C\u2500';
96
+
97
+ // Role status
98
+ const hasActive = roleSessions.some(s => s.status === 'active');
99
+ const statusIcon = hasActive ? '\u25CF' : '\u25CB';
100
+ const statusColor = hasActive ? 'green' : 'gray';
101
+
78
102
  lines.push({
79
103
  id: ++sysLineId,
80
- text: ` ${statusIcon} ${s.roleId.padEnd(14)} ${portInfo}${worktree ? ' ' + worktree : ''}`,
104
+ text: ` ${branch} ${statusIcon} ${roleId} (${roleSessions.length})`,
81
105
  color: statusColor,
82
106
  });
83
- if (s.task) {
107
+
108
+ // Sessions under this role
109
+ const indent = isLastRole ? ' ' : ' \u2502 ';
110
+ for (const sess of roleSessions.slice(-3)) { // Show last 3 sessions per role
111
+ const port = portMap.get(sess.id);
112
+ const portStr = port ? `API:${port.ports.api} Vite:${port.ports.vite}` : '';
113
+ const worktree = port?.worktreePath ? `\u{1F33F}${port.worktreePath.split('/').pop()}` : '';
114
+ const statusStr = sess.status === 'active' ? '\u25CF' : '\u25CB';
115
+ const sesColor = sess.status === 'active' ? 'white' : 'gray';
116
+
117
+ lines.push({
118
+ id: ++sysLineId,
119
+ text: `${indent}${statusStr} ${sess.id.slice(0, 22)} ${portStr} ${worktree}`,
120
+ color: sesColor,
121
+ });
122
+ }
123
+ if (roleSessions.length > 3) {
84
124
  lines.push({
85
125
  id: ++sysLineId,
86
- text: ` ${s.task.slice(0, 60)}`,
126
+ text: `${indent} ... +${roleSessions.length - 3} more`,
87
127
  color: 'gray',
88
128
  });
89
129
  }
90
130
  }
91
131
  }
92
132
 
93
- // Unlinked sessions (not associated with any wave)
133
+ // Unlinked active sessions
94
134
  if (unlinked.length > 0) {
95
135
  lines.push({ id: ++sysLineId, text: '', color: 'white' });
96
- lines.push({ id: ++sysLineId, text: 'Unlinked sessions:', color: 'yellow' });
97
- for (const s of unlinked) {
98
- const portInfo = s.ports.api ? `API:${s.ports.api} Vite:${s.ports.vite}` : '(no ports)';
136
+ lines.push({ id: ++sysLineId, text: `Unlinked (${unlinked.length}):`, color: 'yellow' });
137
+ for (const s of unlinked.slice(-5)) {
138
+ const port = portMap.get(s.id);
139
+ const portStr = port ? `API:${port.ports.api}` : '';
99
140
  lines.push({
100
141
  id: ++sysLineId,
101
- text: ` ${s.roleId.padEnd(14)} ${portInfo} ${s.task?.slice(0, 40) ?? ''}`,
142
+ text: ` ${s.roleId.padEnd(12)} ${s.id.slice(0, 22)} ${portStr}`,
102
143
  color: 'gray',
103
144
  });
104
145
  }
@@ -317,7 +358,7 @@ export const App: React.FC = () => {
317
358
  break;
318
359
  }
319
360
  case 'agents': {
320
- const lines = formatAgentsTree(waves, api.activeSessions, focusedWaveId);
361
+ const lines = formatAgentsTree(waves, api.sessions, api.activeSessions, focusedWaveId);
321
362
  addSystemLines(lines);
322
363
  break;
323
364
  }
@@ -333,9 +374,15 @@ export const App: React.FC = () => {
333
374
  addSystemMessage(`Sessions (${api.activeSessions.length}):`, 'cyan');
334
375
  for (const s of api.activeSessions) {
335
376
  const alive = s.alive === false ? ' DEAD' : s.pid ? ` PID:${s.pid}` : '';
336
- const wave = s.waveId ? ` wave=${String(s.waveId).replace('wave-', 'W')}` : '';
377
+ // Find wave index
378
+ let waveLabel = '';
379
+ if (s.waveId) {
380
+ const wi = waves.findIndex(w => w.waveId === s.waveId);
381
+ waveLabel = wi >= 0 ? ` W${wi + 1}` : ` ${String(s.waveId).replace('wave-', 'W')}`;
382
+ }
383
+ const worktree = s.worktreePath ? ` \u{1F33F}${s.worktreePath.split('/').pop()}` : '';
337
384
  addSystemMessage(
338
- ` ${s.sessionId.slice(0, 25).padEnd(26)} ${s.roleId.padEnd(12)} API:${s.ports.api} ${s.status}${alive}${wave}`,
385
+ ` ${s.sessionId.slice(0, 22).padEnd(23)} ${s.roleId.padEnd(10)} :${s.ports.api}/:${s.ports.vite} ${s.status}${alive}${waveLabel}${worktree}`,
339
386
  s.alive === false ? 'red' : s.status === 'active' ? 'green' : 'gray'
340
387
  );
341
388
  }