tycono 0.1.96-beta.19 → 0.1.96-beta.20

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.19",
3
+ "version": "0.1.96-beta.20",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/tui/app.tsx CHANGED
@@ -427,6 +427,10 @@ export const App: React.FC = () => {
427
427
  selectedRoleId={selectedRoleId}
428
428
  streamStatus={sse.streamStatus}
429
429
  waveId={focusedWaveId}
430
+ activeSessions={api.activeSessions}
431
+ waves={waves}
432
+ focusedWaveId={focusedWaveId}
433
+ portSummary={api.portSummary}
430
434
  onMove={(dir) => {
431
435
  if (dir === 'up') {
432
436
  setSelectedRoleIndex(Math.max(0, selectedRoleIndex - 1));
@@ -1,5 +1,10 @@
1
1
  /**
2
- * PanelMode — Tab view: Org Tree (left) + selected Role's stream (right)
2
+ * PanelMode — Tab view: Org Tree (left) + Agent Detail + Stream (right)
3
+ *
4
+ * Left: Org Tree with status icons
5
+ * + compact resource summary (waves, ports)
6
+ * Right: Selected role's resource info (port, worktree, browser)
7
+ * + event stream
3
8
  *
4
9
  * Navigation:
5
10
  * j/k or arrow keys — move in Org Tree
@@ -12,7 +17,8 @@ import { Box, Text, useInput } from 'ink';
12
17
  import { OrgTree } from './OrgTree';
13
18
  import { StreamView } from './StreamView';
14
19
  import type { OrgNode } from '../store';
15
- import type { SSEEvent } from '../api';
20
+ import type { SSEEvent, ActiveSessionInfo } from '../api';
21
+ import type { WaveInfo } from '../hooks/useCommand';
16
22
 
17
23
  interface PanelModeProps {
18
24
  tree: OrgNode[];
@@ -22,11 +28,31 @@ interface PanelModeProps {
22
28
  selectedRoleId: string | null;
23
29
  streamStatus: 'idle' | 'streaming' | 'done' | 'error';
24
30
  waveId: string | null;
31
+ activeSessions: ActiveSessionInfo[];
32
+ waves: WaveInfo[];
33
+ focusedWaveId: string | null;
34
+ portSummary: { active: number; totalPorts: number };
25
35
  onMove: (direction: 'up' | 'down') => void;
26
36
  onSelect: () => void;
27
37
  onEscape: () => void;
28
38
  }
29
39
 
40
+ /** Find active session for a given roleId */
41
+ function findSessionForRole(activeSessions: ActiveSessionInfo[], roleId: string): ActiveSessionInfo | null {
42
+ // Prefer active sessions, then any
43
+ return activeSessions.find(s => s.roleId === roleId && s.status === 'active')
44
+ ?? activeSessions.find(s => s.roleId === roleId)
45
+ ?? null;
46
+ }
47
+
48
+ /** Format elapsed time */
49
+ function elapsed(startedAt: string): string {
50
+ const ms = Date.now() - new Date(startedAt).getTime();
51
+ if (ms < 60_000) return `${Math.floor(ms / 1000)}s`;
52
+ if (ms < 3600_000) return `${Math.floor(ms / 60_000)}m`;
53
+ return `${Math.floor(ms / 3600_000)}h`;
54
+ }
55
+
30
56
  export const PanelMode: React.FC<PanelModeProps> = ({
31
57
  tree,
32
58
  flatRoles,
@@ -35,6 +61,10 @@ export const PanelMode: React.FC<PanelModeProps> = ({
35
61
  selectedRoleId,
36
62
  streamStatus,
37
63
  waveId,
64
+ activeSessions,
65
+ waves,
66
+ focusedWaveId,
67
+ portSummary,
38
68
  onMove,
39
69
  onSelect,
40
70
  onEscape,
@@ -75,11 +105,30 @@ export const PanelMode: React.FC<PanelModeProps> = ({
75
105
  ? flatRoles.includes(selectedRoleId) ? selectedRoleId : 'All'
76
106
  : 'All';
77
107
 
108
+ // Find resource info for selected role
109
+ const selectedSession = selectedRoleId
110
+ ? findSessionForRole(activeSessions, selectedRoleId)
111
+ : null;
112
+
113
+ // Focused wave info
114
+ const focusedWave = waves.find(w => w.waveId === focusedWaveId);
115
+ const focusedWaveIndex = focusedWaveId
116
+ ? waves.findIndex(w => w.waveId === focusedWaveId) + 1
117
+ : 0;
118
+
119
+ // Count sessions per wave for summary
120
+ const waveSessionCounts = new Map<string, number>();
121
+ for (const s of activeSessions) {
122
+ if (s.waveId) {
123
+ waveSessionCounts.set(s.waveId, (waveSessionCounts.get(s.waveId) ?? 0) + 1);
124
+ }
125
+ }
126
+
78
127
  return (
79
128
  <Box flexDirection="column" flexGrow={1}>
80
- {/* Main content: Org Tree left | Stream right */}
129
+ {/* Main content: Org Tree left | Detail + Stream right */}
81
130
  <Box flexGrow={1}>
82
- {/* Left: Org Tree */}
131
+ {/* Left: Org Tree + Resource Summary */}
83
132
  <Box flexDirection="column" width={28}>
84
133
  <OrgTree
85
134
  tree={tree}
@@ -87,15 +136,109 @@ export const PanelMode: React.FC<PanelModeProps> = ({
87
136
  selectedIndex={selectedRoleIndex}
88
137
  flatRoles={flatRoles}
89
138
  />
139
+
140
+ {/* Resource Summary — below org tree */}
141
+ <Box flexDirection="column" paddingX={1} marginTop={1}>
142
+ <Text color="gray">{'\u2500'.repeat(24)}</Text>
143
+
144
+ {/* Waves */}
145
+ {waves.length > 0 && (
146
+ <Box flexDirection="column">
147
+ {waves.map((w, i) => {
148
+ const isFocused = w.waveId === focusedWaveId;
149
+ const count = waveSessionCounts.get(w.waveId) ?? 0;
150
+ return (
151
+ <Box key={w.waveId}>
152
+ <Text color={isFocused ? 'green' : 'gray'}>
153
+ {isFocused ? '\u25B8' : ' '} W{i + 1}
154
+ </Text>
155
+ <Text color="gray"> {count > 0 ? `${count} agents` : 'idle'}</Text>
156
+ </Box>
157
+ );
158
+ })}
159
+ </Box>
160
+ )}
161
+
162
+ {/* Port summary */}
163
+ {portSummary.totalPorts > 0 && (
164
+ <Box marginTop={0}>
165
+ <Text color="blue">{portSummary.totalPorts} ports</Text>
166
+ <Text color="gray"> allocated</Text>
167
+ </Box>
168
+ )}
169
+ </Box>
90
170
  </Box>
91
171
 
92
172
  {/* Vertical separator — fill available height */}
93
173
  <Box flexDirection="column" marginX={0}>
94
- <Text color="gray">{'│\n'.repeat(Math.max(5, termHeight - 6))}</Text>
174
+ <Text color="gray">{'\u2502\n'.repeat(Math.max(5, termHeight - 6))}</Text>
95
175
  </Box>
96
176
 
97
- {/* Right: Stream for selected role */}
177
+ {/* Right: Agent Detail + Stream */}
98
178
  <Box flexGrow={1} flexDirection="column" overflow="hidden">
179
+ {/* Agent Resource Header — shown when a role is selected */}
180
+ {selectedRoleId && selectedSession && (
181
+ <Box flexDirection="column" paddingX={1} marginBottom={0}>
182
+ <Box justifyContent="space-between">
183
+ <Text bold color="cyan">{selectedRoleId}</Text>
184
+ <Text color={selectedSession.status === 'active' ? 'green' : 'gray'}>
185
+ {selectedSession.status === 'active' ? '\u25CF' : '\u25CB'} {selectedSession.status}
186
+ {selectedSession.startedAt ? ` (${elapsed(selectedSession.startedAt)})` : ''}
187
+ </Text>
188
+ </Box>
189
+
190
+ {/* Ports */}
191
+ <Box>
192
+ <Text color="gray">Port </Text>
193
+ <Text color="white">
194
+ API:{selectedSession.ports.api} Vite:{selectedSession.ports.vite}
195
+ {selectedSession.ports.hmr ? ` HMR:${selectedSession.ports.hmr}` : ''}
196
+ </Text>
197
+ </Box>
198
+
199
+ {/* Worktree */}
200
+ {selectedSession.worktreePath && (
201
+ <Box>
202
+ <Text color="gray">Tree </Text>
203
+ <Text color="white">{selectedSession.worktreePath}</Text>
204
+ </Box>
205
+ )}
206
+
207
+ {/* Wave association */}
208
+ {selectedSession.waveId && (
209
+ <Box>
210
+ <Text color="gray">Wave </Text>
211
+ <Text color="white">
212
+ {(() => {
213
+ const wi = waves.findIndex(w => w.waveId === selectedSession.waveId);
214
+ return wi >= 0 ? `Wave ${wi + 1}` : selectedSession.waveId;
215
+ })()}
216
+ </Text>
217
+ </Box>
218
+ )}
219
+
220
+ {/* Task */}
221
+ {selectedSession.task && (
222
+ <Box>
223
+ <Text color="gray">Task </Text>
224
+ <Text color="white">{selectedSession.task.slice(0, 60)}</Text>
225
+ </Box>
226
+ )}
227
+
228
+ <Text color="gray">{'\u2500'.repeat(40)}</Text>
229
+ </Box>
230
+ )}
231
+
232
+ {/* Agent Resource Header — role selected but no active session */}
233
+ {selectedRoleId && !selectedSession && (
234
+ <Box flexDirection="column" paddingX={1} marginBottom={0}>
235
+ <Text bold color="cyan">{selectedRoleId}</Text>
236
+ <Text color="gray">(no active session)</Text>
237
+ <Text color="gray">{'\u2500'.repeat(40)}</Text>
238
+ </Box>
239
+ )}
240
+
241
+ {/* Stream */}
99
242
  <StreamView
100
243
  events={roleEvents}
101
244
  allRoleIds={flatRoles}
@@ -108,7 +251,7 @@ export const PanelMode: React.FC<PanelModeProps> = ({
108
251
 
109
252
  {/* Separator */}
110
253
  <Box width="100%">
111
- <Text color="gray">{''.repeat(process.stdout.columns || 70)}</Text>
254
+ <Text color="gray">{'\u2500'.repeat(process.stdout.columns || 70)}</Text>
112
255
  </Box>
113
256
 
114
257
  {/* Footer hints */}