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 +1 -1
- package/src/tui/api.ts +1 -0
- package/src/tui/app.tsx +75 -28
package/package.json
CHANGED
package/src/tui/api.ts
CHANGED
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
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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,
|
|
47
|
-
const unlinked:
|
|
48
|
-
|
|
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
|
-
|
|
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}:
|
|
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: ' (
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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: ` ${
|
|
104
|
+
text: ` ${branch} ${statusIcon} ${roleId} (${roleSessions.length})`,
|
|
81
105
|
color: statusColor,
|
|
82
106
|
});
|
|
83
|
-
|
|
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:
|
|
126
|
+
text: `${indent} ... +${roleSessions.length - 3} more`,
|
|
87
127
|
color: 'gray',
|
|
88
128
|
});
|
|
89
129
|
}
|
|
90
130
|
}
|
|
91
131
|
}
|
|
92
132
|
|
|
93
|
-
// Unlinked sessions
|
|
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:
|
|
97
|
-
for (const s of unlinked) {
|
|
98
|
-
const
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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
|
}
|