tycono 0.3.11 → 0.3.12-beta.0

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.3.11",
3
+ "version": "0.3.12-beta.0",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/tui/app.tsx CHANGED
@@ -326,12 +326,17 @@ export const App: React.FC = () => {
326
326
  return 'idle' as const;
327
327
  }, [sse.streamStatus, activeCount]);
328
328
 
329
- // Focused wave index (1-based)
329
+ // Focused wave index (1-based) + startedAt
330
330
  const focusedWaveIndex = useMemo(() => {
331
331
  if (!focusedWaveId) return 0;
332
332
  return waves.findIndex(w => w.waveId === focusedWaveId) + 1;
333
333
  }, [focusedWaveId, waves]);
334
334
 
335
+ const focusedWaveStartedAt = useMemo(() => {
336
+ if (!focusedWaveId) return 0;
337
+ return waves.find(w => w.waveId === focusedWaveId)?.startedAt ?? 0;
338
+ }, [focusedWaveId, waves]);
339
+
335
340
  // Command handler
336
341
  const { execute } = useCommand({
337
342
  focusedWaveId,
@@ -375,12 +380,18 @@ export const App: React.FC = () => {
375
380
  if (waves.length === 0) {
376
381
  addSystemMessage('No waves.', 'gray');
377
382
  } else {
383
+ // Show recent 8 waves max to prevent scrollback overflow
384
+ const MAX_SHOW = 8;
385
+ const showWaves = waves.length > MAX_SHOW ? waves.slice(-MAX_SHOW) : waves;
386
+ const skipped = waves.length - showWaves.length;
387
+ if (skipped > 0) addSystemMessage(`(+${skipped} older waves)`, 'gray');
378
388
  addSystemMessage('Waves:', 'cyan');
379
- waves.forEach((w, i) => {
389
+ showWaves.forEach((w) => {
390
+ const idx = waves.indexOf(w);
380
391
  const isFocused = w.waveId === focusedWaveId;
381
392
  const prefix = isFocused ? '*' : ' ';
382
393
  const label = w.directive ? w.directive.slice(0, 60) : '(idle)';
383
- addSystemMessage(`${prefix}${i + 1}. Wave ${i + 1} \u2014 ${label}`, isFocused ? 'green' : 'white');
394
+ addSystemMessage(`${prefix}${idx + 1}. Wave ${idx + 1} \u2014 ${label}`, isFocused ? 'green' : 'white');
384
395
  });
385
396
  }
386
397
  break;
@@ -630,6 +641,8 @@ export const App: React.FC = () => {
630
641
  onQuickAction={(action) => {
631
642
  handleCommandSubmit(`/${action}`);
632
643
  }}
644
+ activeSessions={api.activeSessions}
645
+ focusedWaveId={focusedWaveId}
633
646
  />
634
647
  <StatusBar
635
648
  companyName={api.company?.name ?? 'Loading...'}
@@ -639,6 +652,9 @@ export const App: React.FC = () => {
639
652
  activeCount={activeCount}
640
653
  portCount={api.portSummary.totalPorts}
641
654
  totalCost={0}
655
+ activeSessions={api.activeSessions}
656
+ focusedWaveId={focusedWaveId}
657
+ waveStartedAt={focusedWaveStartedAt}
642
658
  />
643
659
  </Box>
644
660
  );
@@ -8,7 +8,7 @@
8
8
  import React, { useState, useCallback, useRef } from 'react';
9
9
  import { Box, Text, Static, useInput } from 'ink';
10
10
  import TextInput from 'ink-text-input';
11
- import type { SSEEvent } from '../api';
11
+ import type { SSEEvent, ActiveSessionInfo } from '../api';
12
12
  import { getRoleColor } from '../theme';
13
13
  // Markdown rendering is done inline via regex (no external dependency)
14
14
 
@@ -30,6 +30,8 @@ interface CommandModeProps {
30
30
  systemMessages: StreamLine[];
31
31
  onSubmit: (input: string) => void;
32
32
  onQuickAction?: (action: string) => void;
33
+ activeSessions?: ActiveSessionInfo[];
34
+ focusedWaveId?: string | null;
33
35
  }
34
36
 
35
37
  let lineCounter = 0;
@@ -290,6 +292,8 @@ export const CommandMode: React.FC<CommandModeProps> = ({
290
292
  systemMessages,
291
293
  onSubmit,
292
294
  onQuickAction,
295
+ activeSessions,
296
+ focusedWaveId,
293
297
  }) => {
294
298
  const [input, setInput] = useState('');
295
299
  const committedRef = useRef(0);
@@ -297,9 +301,15 @@ export const CommandMode: React.FC<CommandModeProps> = ({
297
301
  const [quickBarActive, setQuickBarActive] = useState(false);
298
302
  const [quickBarIndex, setQuickBarIndex] = useState(0);
299
303
 
300
- // Convert events to stream lines
304
+ // Convert events to stream lines (collapse consecutive thinking from same role)
301
305
  const eventLines: StreamLine[] = [];
302
- for (const event of events) {
306
+ for (let i = 0; i < events.length; i++) {
307
+ const event = events[i];
308
+ // Skip thinking events if next event from same role is also thinking
309
+ if (event.type === 'thinking' && i + 1 < events.length) {
310
+ const next = events[i + 1];
311
+ if (next.type === 'thinking' && next.roleId === event.roleId) continue;
312
+ }
303
313
  const line = summarizeEvent(event, allRoleIds);
304
314
  if (line) eventLines.push(line);
305
315
  }
@@ -387,6 +397,25 @@ export const CommandMode: React.FC<CommandModeProps> = ({
387
397
  </Box>
388
398
  )}
389
399
 
400
+ {/* Live mini-tree — shows active roles during dispatch */}
401
+ {(() => {
402
+ if (!activeSessions || !focusedWaveId) return null;
403
+ const waveRoles = activeSessions
404
+ .filter(s => s.waveId === focusedWaveId && s.status === 'active')
405
+ .map(s => s.roleId);
406
+ const unique = [...new Set(waveRoles)];
407
+ if (unique.length === 0) return null;
408
+ return (
409
+ <Box paddingX={0}>
410
+ <Text color="gray">{unique.map((r, i) => {
411
+ const dot = '\u25CF';
412
+ const arrow = i < unique.length - 1 ? '\u2192' : '';
413
+ return `${dot}${r}${arrow}`;
414
+ }).join('')}</Text>
415
+ </Box>
416
+ );
417
+ })()}
418
+
390
419
  {/* Input */}
391
420
  <Box paddingX={0} marginTop={0}>
392
421
  <Text color={quickBarActive ? 'gray' : 'yellow'} bold>&gt; </Text>
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * StatusBar — bottom bar (Claude Code style)
3
- * Shows: company name, wave index [focused/total], active roles, ports, cost
3
+ * Shows: company name, wave info, dispatch chain, active roles, elapsed, cost
4
4
  */
5
5
 
6
6
  import React from 'react';
7
7
  import { Box, Text } from 'ink';
8
+ import type { ActiveSessionInfo } from '../api';
8
9
 
9
10
  interface StatusBarProps {
10
11
  companyName: string;
@@ -14,6 +15,17 @@ interface StatusBarProps {
14
15
  activeCount: number;
15
16
  portCount: number; // total allocated ports
16
17
  totalCost: number;
18
+ activeSessions?: ActiveSessionInfo[];
19
+ focusedWaveId?: string | null;
20
+ waveStartedAt?: number; // timestamp of focused wave start
21
+ }
22
+
23
+ function elapsed(ms: number): string {
24
+ const s = Math.floor(ms / 1000);
25
+ if (s < 60) return `${s}s`;
26
+ const m = Math.floor(s / 60);
27
+ if (m < 60) return `${m}m${s % 60}s`;
28
+ return `${Math.floor(m / 60)}h${m % 60}m`;
17
29
  }
18
30
 
19
31
  export const StatusBar: React.FC<StatusBarProps> = ({
@@ -24,6 +36,9 @@ export const StatusBar: React.FC<StatusBarProps> = ({
24
36
  activeCount,
25
37
  portCount,
26
38
  totalCost,
39
+ activeSessions,
40
+ focusedWaveId,
41
+ waveStartedAt,
27
42
  }) => {
28
43
  const statusDot = waveStatus === 'running' ? ' \u25CF'
29
44
  : waveStatus === 'done' ? ' \u2713'
@@ -36,6 +51,25 @@ export const StatusBar: React.FC<StatusBarProps> = ({
36
51
  // Show [1/3] only when 2+ waves
37
52
  const countLabel = waveCount >= 2 ? ` [${waveIndex}/${waveCount}]` : '';
38
53
 
54
+ // Dispatch chain: show active roles for focused wave
55
+ let chainLabel = '';
56
+ if (activeSessions && focusedWaveId && waveStatus === 'running') {
57
+ const waveRoles = activeSessions
58
+ .filter(s => s.waveId === focusedWaveId && s.status === 'active')
59
+ .map(s => s.roleId);
60
+ // Deduplicate and show chain
61
+ const unique = [...new Set(waveRoles)];
62
+ if (unique.length > 0) {
63
+ chainLabel = unique.join('\u2192');
64
+ }
65
+ }
66
+
67
+ // Elapsed time
68
+ let elapsedLabel = '';
69
+ if (waveStartedAt && waveStatus === 'running') {
70
+ elapsedLabel = elapsed(Date.now() - waveStartedAt);
71
+ }
72
+
39
73
  return (
40
74
  <Box width="100%" paddingX={1}>
41
75
  <Text color="cyan" bold>Tycono</Text>
@@ -49,7 +83,19 @@ export const StatusBar: React.FC<StatusBarProps> = ({
49
83
  </Text>
50
84
  </>
51
85
  )}
52
- {activeCount > 0 && (
86
+ {chainLabel && (
87
+ <>
88
+ <Text color="gray"> | </Text>
89
+ <Text color="yellow">{chainLabel}</Text>
90
+ </>
91
+ )}
92
+ {elapsedLabel && (
93
+ <>
94
+ <Text color="gray"> </Text>
95
+ <Text color="gray">{elapsedLabel}</Text>
96
+ </>
97
+ )}
98
+ {!chainLabel && activeCount > 0 && (
53
99
  <>
54
100
  <Text color="gray"> | </Text>
55
101
  <Text color="yellow">{activeCount} active</Text>