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 +2 -2
- package/src/tui/app.tsx +3 -46
- package/src/tui/components/CommandMode.tsx +13 -27
- package/src/tui/components/OrgTree.tsx +15 -82
- package/src/tui/components/PanelMode.tsx +292 -463
- package/src/tui/components/StreamView.tsx +45 -113
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tycono",
|
|
3
|
-
"version": "0.3.
|
|
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": "^
|
|
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
|
|
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
|
-
|
|
87
|
-
|
|
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,
|
|
139
|
-
else if (inp.
|
|
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
|
-
|
|
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
|
|
410
|
+
// Show user input with visual separator for emphasis
|
|
425
411
|
setUserInputs(prev => [...prev.slice(-10), {
|
|
426
412
|
id: ++lineCounter,
|
|
427
|
-
text:
|
|
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
|
|
3
|
-
*
|
|
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
|
|
20
|
-
|
|
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
|
-
|
|
26
|
+
prefix += isLast[j] ? ' ' : '\u2502 ';
|
|
52
27
|
}
|
|
53
|
-
|
|
54
|
-
|
|
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,
|
|
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
|
|
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
|
-
<
|
|
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
|
});
|