tycono 0.3.14-beta.9 → 0.3.15

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.14-beta.9",
3
+ "version": "0.3.15",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "cors": "^2.8.5",
36
36
  "dotenv": "^16.4.7",
37
37
  "express": "^5.0.1",
38
- "glob": "^11.0.1",
38
+ "glob": "^13.0.6",
39
39
  "gray-matter": "^4.0.3",
40
40
  "ink": "^5.2.1",
41
41
  "ink-select-input": "^6.2.0",
package/src/tui/app.tsx CHANGED
@@ -14,8 +14,6 @@
14
14
  import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
15
15
  import { Box, Text, useApp, useInput } from 'ink';
16
16
  import { StatusBar } from './components/StatusBar';
17
- import { OrgTree } from './components/OrgTree';
18
- import { StreamView } from './components/StreamView';
19
17
  import { CommandMode, type StreamLine } from './components/CommandMode';
20
18
  import { PanelMode } from './components/PanelMode';
21
19
  import { SetupWizard } from './components/SetupWizard';
@@ -632,6 +630,8 @@ export const App: React.FC = () => {
632
630
  return;
633
631
  }
634
632
  if (mode === 'command' && key.tab) {
633
+ // Clear terminal before Panel Mode (removes Command Mode scrollback)
634
+ process.stdout.write('\x1b[2J\x1b[H');
635
635
  setMode('panel');
636
636
  }
637
637
  });
@@ -670,52 +670,9 @@ export const App: React.FC = () => {
670
670
  // Command Mode: scrollable terminal (no fullscreen)
671
671
  // Panel Mode: fullscreen (intentional — like vim for inspection)
672
672
  if (mode === 'panel') {
673
- // OOM debug levels: 0=full, 1=minimal, 2=orgTree only, 3=stream only
674
- const debugLevel = parseInt(process.env.PANEL_MINIMAL || '0', 10);
675
- if (debugLevel === 1) {
676
- return (
677
- <Box flexDirection="column">
678
- <Text color="cyan">Panel Mode (minimal)</Text>
679
- <Text color="gray">Events: {sse.events.length} | Press Esc</Text>
680
- </Box>
681
- );
682
- }
683
- if (debugLevel === 2) {
684
- return (
685
- <Box flexDirection="column">
686
- <OrgTree tree={orgTree} focused={true} selectedIndex={0} flatRoles={flatRoleIds} ceoStatus="idle" />
687
- <Text color="gray">OrgTree only | Press Esc</Text>
688
- </Box>
689
- );
690
- }
691
- if (debugLevel === 3) {
692
- return (
693
- <Box flexDirection="column">
694
- <StreamView events={sse.events} allRoleIds={flatRoleIds} streamStatus={sse.streamStatus} waveId={focusedWaveId} roleLabel="All" />
695
- <Text color="gray">StreamView only | Press Esc</Text>
696
- </Box>
697
- );
698
- }
699
- if (debugLevel === 4) {
700
- // Full layout structure but empty content
701
- return (
702
- <Box flexDirection="column" height={termHeight}>
703
- <Box flexGrow={1}>
704
- <Box flexDirection="column" width={28}>
705
- <Text color="green">Left Panel</Text>
706
- </Box>
707
- <Text color="gray">{'\u2502'}</Text>
708
- <Box flexGrow={1} flexDirection="column" overflow="hidden">
709
- <Text color="cyan">Right Panel</Text>
710
- </Box>
711
- </Box>
712
- <StatusBar companyName="test" waveIndex={1} waveCount={1} waveStatus="idle" activeCount={0} portCount={0} totalCost={0} />
713
- </Box>
714
- );
715
- }
716
673
  return (
717
674
  <Box flexDirection="column">
718
- <Box flexGrow={1} flexDirection="column">
675
+ <Box flexDirection="column">
719
676
  <PanelMode
720
677
  tree={orgTree}
721
678
  flatRoles={flatRoleIds}
@@ -83,16 +83,8 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
83
83
  }
84
84
 
85
85
  case 'thinking': {
86
- const text = ((event.data.text as string) ?? '').slice(0, 120);
87
- if (!text.trim()) return null;
88
- return {
89
- id: ++lineCounter,
90
- prefix: isSupervisor ? undefined : event.roleId,
91
- prefixColor: roleColor,
92
- text: `\uD83D\uDCAD ${text}`,
93
- color: 'gray',
94
- indent: !isSupervisor,
95
- };
86
+ // Hide thinking by default internal noise for user
87
+ return null;
96
88
  }
97
89
 
98
90
  case 'dispatch:start': {
@@ -130,17 +122,18 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
130
122
 
131
123
  case 'tool:start': {
132
124
  const toolName = (event.data.name as string) ?? 'tool';
125
+ // Only show Write/Edit (file changes) + Bash (commands). Hide Read/Grep/Glob (noise).
126
+ const isWrite = ['Write', 'Edit', 'NotebookEdit'].includes(toolName);
127
+ if (!isWrite) return null; // Only show file writes — hide Read/Grep/Glob/Bash
128
+
133
129
  const input = event.data.input;
134
130
  let detail = '';
135
131
  if (input && typeof input === 'object') {
136
132
  const inp = input as Record<string, unknown>;
137
- if (inp.file_path) detail = ` ${String(inp.file_path)}`;
138
- else if (inp.command) detail = ` ${String(inp.command).slice(0, 80)}`;
139
- else if (inp.pattern) detail = ` ${String(inp.pattern)}`;
140
- else if (inp.description) detail = ` ${String(inp.description).slice(0, 60)}`;
133
+ if (inp.file_path) detail = ` ${String(inp.file_path).split('/').slice(-2).join('/')}`;
134
+ else if (inp.command) detail = ` ${String(inp.command).slice(0, 60)}`;
135
+ else if (inp.description) detail = ` ${String(inp.description).slice(0, 40)}`;
141
136
  }
142
- // Highlight file writes
143
- const isWrite = ['Write', 'Edit'].includes(toolName);
144
137
  return {
145
138
  id: ++lineCounter,
146
139
  prefix: isSupervisor ? undefined : event.roleId,
@@ -152,15 +145,8 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
152
145
  }
153
146
 
154
147
  case 'tool:result': {
155
- const toolName = (event.data.name as string) ?? 'tool';
156
- return {
157
- id: ++lineCounter,
158
- prefix: isSupervisor ? undefined : event.roleId,
159
- prefixColor: roleColor,
160
- text: ` \u2190 ${toolName} done`,
161
- color: 'gray',
162
- indent: !isSupervisor,
163
- };
148
+ // Hide tool results tool:start is sufficient
149
+ return null;
164
150
  }
165
151
 
166
152
  case 'msg:start': {
@@ -421,10 +407,10 @@ export const CommandMode: React.FC<CommandModeProps> = ({
421
407
  const handleSubmit = useCallback((value: string) => {
422
408
  const trimmed = value.trim();
423
409
  if (trimmed) {
424
- // Show user input immediately in scrollback (before AI responds)
410
+ // Show user input with visual separator for emphasis
425
411
  setUserInputs(prev => [...prev.slice(-10), {
426
412
  id: ++lineCounter,
427
- text: `> ${trimmed}`,
413
+ text: `\u2501\u2501 > ${trimmed}`,
428
414
  color: 'green',
429
415
  }]);
430
416
  onSubmit(trimmed);
@@ -1,6 +1,6 @@
1
1
  /**
2
- * OrgTree — left panel showing organization hierarchy with real-time status
3
- * CEO is now selectable (index 0 in flatRoles)
2
+ * OrgTree — left panel showing organization hierarchy
3
+ * Simplified to single Text render to prevent yoga OOM on wide terminals
4
4
  */
5
5
 
6
6
  import React from 'react';
@@ -16,103 +16,36 @@ interface OrgTreeProps {
16
16
  ceoStatus?: string;
17
17
  }
18
18
 
19
- function statusColor(status: string): string {
20
- switch (status) {
21
- case 'working':
22
- case 'streaming':
23
- return 'green';
24
- case 'done':
25
- return 'gray';
26
- case 'error':
27
- return 'red';
28
- case 'awaiting_input':
29
- return 'yellow';
30
- default:
31
- return 'gray';
32
- }
33
- }
34
-
35
- interface FlatEntry {
36
- roleId: string;
37
- level: string;
38
- status: string;
39
- prefix: string;
40
- }
41
-
42
- function flattenTree(nodes: OrgNode[], prefix: string = '', isLast: boolean[] = []): FlatEntry[] {
43
- const result: FlatEntry[] = [];
44
-
19
+ function flattenTree(nodes: OrgNode[], isLast: boolean[] = []): Array<{ roleId: string; status: string; line: string }> {
20
+ const result: Array<{ roleId: string; status: string; line: string }> = [];
45
21
  for (let i = 0; i < nodes.length; i++) {
46
22
  const node = nodes[i];
47
23
  const last = i === nodes.length - 1;
48
-
49
- let linePrefix = '';
24
+ let prefix = '';
50
25
  for (let j = 0; j < isLast.length; j++) {
51
- linePrefix += isLast[j] ? ' ' : '\u2502 ';
26
+ prefix += isLast[j] ? ' ' : '\u2502 ';
52
27
  }
53
- linePrefix += isLast.length > 0 || i > 0 || nodes.length > 1
54
- ? (last ? '\u2514\u2500 ' : '\u251C\u2500 ')
55
- : '';
56
-
57
- result.push({
58
- roleId: node.role.id,
59
- level: node.role.level,
60
- status: node.status,
61
- prefix: linePrefix,
62
- });
63
-
28
+ prefix += last ? '\u2514\u2500 ' : '\u251C\u2500 ';
29
+ const icon = statusIcon(node.status);
30
+ result.push({ roleId: node.role.id, status: node.status, line: `${prefix}${icon} ${node.role.id}` });
64
31
  if (node.children.length > 0) {
65
- result.push(...flattenTree(node.children, '', [...isLast, last]));
32
+ result.push(...flattenTree(node.children, [...isLast, last]));
66
33
  }
67
34
  }
68
-
69
35
  return result;
70
36
  }
71
37
 
72
38
  export const OrgTree: React.FC<OrgTreeProps> = React.memo(({ tree, focused, selectedIndex, flatRoles, ceoStatus }) => {
73
- const entries = flattenTree(tree);
74
- const isCeoSelected = focused && flatRoles[selectedIndex] === 'ceo';
75
39
  const ceoIcon = statusIcon(ceoStatus ?? 'idle');
76
- const ceoColor = statusColor(ceoStatus ?? 'idle');
40
+ const entries = flattenTree(tree);
41
+
42
+ // Render entire tree as single Text block (1 yoga node instead of 50+)
43
+ const lines = [`${ceoIcon} CEO`, ...entries.map(e => e.line)];
77
44
 
78
45
  return (
79
46
  <Box flexDirection="column" paddingX={1}>
80
47
  <Text bold color={focused ? 'cyan' : 'gray'}>{'\u2500\u2500 Org Tree \u2500\u2500'}</Text>
81
- <Box marginTop={1}>
82
- <Text color={ceoColor} bold={ceoStatus === 'working'}>{ceoIcon} </Text>
83
- <Text
84
- color={isCeoSelected ? 'cyan' : 'yellow'}
85
- bold={isCeoSelected}
86
- inverse={isCeoSelected}
87
- >
88
- CEO
89
- </Text>
90
- </Box>
91
- {entries.map((entry, i) => {
92
- const isSelected = focused && flatRoles[selectedIndex] === entry.roleId;
93
- const icon = statusIcon(entry.status);
94
- const color = statusColor(entry.status);
95
-
96
- return (
97
- <Box key={entry.roleId + '-' + i}>
98
- <Text color="gray">{entry.prefix}</Text>
99
- <Text
100
- color={color}
101
- bold={entry.status === 'working'}
102
- >
103
- {icon}
104
- </Text>
105
- <Text> </Text>
106
- <Text
107
- color={isSelected ? 'cyan' : 'white'}
108
- bold={isSelected}
109
- inverse={isSelected}
110
- >
111
- {entry.roleId}
112
- </Text>
113
- </Box>
114
- );
115
- })}
48
+ <Text color="white">{'\n' + lines.join('\n')}</Text>
116
49
  </Box>
117
50
  );
118
51
  });