tycono 0.3.14-beta.2 → 0.3.14-beta.21
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/api/src/create-server.ts +2 -0
- package/src/api/src/services/execution-manager.ts +2 -1
- package/src/api/src/services/supervisor-heartbeat.ts +4 -1
- package/src/tui/app.tsx +4 -2
- package/src/tui/components/OrgTree.tsx +15 -82
- package/src/tui/components/PanelMode.tsx +210 -473
- package/src/tui/components/StreamView.tsx +45 -113
- package/src/web/dist/assets/index-C6r_vHBI.js +138 -0
- package/src/web/dist/assets/{index-uwS0YSTU.js → index-Czp8wshq.js} +1 -1
- package/src/web/dist/assets/index-DVKWFwwK.css +1 -0
- package/src/web/dist/assets/{preview-app-CAohaHWp.js → preview-app-CMGFfqT-.js} +1 -1
- package/src/web/dist/index.html +2 -2
- package/src/web/dist/assets/index-A3-TBmWZ.js +0 -138
- package/src/web/dist/assets/index-D1RTvnx7.css +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* StreamView —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* StreamView — stream panel for Panel Mode
|
|
3
|
+
* Simplified to single Text render to prevent yoga OOM on wide terminals.
|
|
4
|
+
* Previous: 30 events × 3 React elements = 90 yoga nodes → OOM on 245+ columns
|
|
5
|
+
* Now: 1 Text element with pre-formatted string
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import React from 'react';
|
|
8
9
|
import { Box, Text } from 'ink';
|
|
9
10
|
import type { SSEEvent } from '../api';
|
|
10
|
-
import { getRoleColor } from '../theme';
|
|
11
11
|
|
|
12
12
|
interface StreamViewProps {
|
|
13
13
|
events: SSEEvent[];
|
|
@@ -21,111 +21,58 @@ function formatTime(ts: string): string {
|
|
|
21
21
|
try {
|
|
22
22
|
const d = new Date(ts);
|
|
23
23
|
return d.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
24
|
-
} catch {
|
|
25
|
-
return '--:--:--';
|
|
26
|
-
}
|
|
24
|
+
} catch { return '--:--:--'; }
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
content: `\u25B6 Started: ${(event.data.task as string)?.replace(/\u26D4[^\u26D4]*\u26D4[^"]*/g, '').trim().slice(0, 80) ?? ''}`,
|
|
34
|
-
contentColor: 'green',
|
|
35
|
-
};
|
|
27
|
+
function eventToLine(event: SSEEvent): string | null {
|
|
28
|
+
const time = formatTime(event.ts);
|
|
29
|
+
const role = event.roleId.padEnd(12);
|
|
36
30
|
|
|
31
|
+
switch (event.type) {
|
|
32
|
+
case 'msg:start': {
|
|
33
|
+
const task = ((event.data.task as string) ?? '').replace(/\u26D4[^\u26D4]*\u26D4[^"]*/g, '').trim().slice(0, 80);
|
|
34
|
+
return `${time} ${role} \u25B6 Started: ${task}`;
|
|
35
|
+
}
|
|
37
36
|
case 'msg:done': {
|
|
38
37
|
const turns = event.data.turns as number | undefined;
|
|
39
|
-
return {
|
|
40
|
-
content: `\u2713 Done${turns ? ` (${turns} turns)` : ''}`,
|
|
41
|
-
contentColor: 'green',
|
|
42
|
-
};
|
|
38
|
+
return `${time} ${role} \u2713 Done${turns ? ` (${turns} turns)` : ''}`;
|
|
43
39
|
}
|
|
44
|
-
|
|
45
40
|
case 'msg:error':
|
|
46
|
-
return {
|
|
47
|
-
content: `\u2717 Error: ${(event.data.error as string ?? event.data.message as string ?? '').slice(0, 120)}`,
|
|
48
|
-
contentColor: 'red',
|
|
49
|
-
};
|
|
50
|
-
|
|
41
|
+
return `${time} ${role} \u2717 ${((event.data.error ?? event.data.message) as string ?? '').slice(0, 80)}`;
|
|
51
42
|
case 'text': {
|
|
52
|
-
const text = ((event.data.text as string) ?? '');
|
|
53
|
-
if (!text
|
|
54
|
-
|
|
55
|
-
return { content: text, contentColor: 'white' };
|
|
43
|
+
const text = ((event.data.text as string) ?? '').trim();
|
|
44
|
+
if (!text) return null;
|
|
45
|
+
return `${time} ${role} ${text.slice(0, 120)}`;
|
|
56
46
|
}
|
|
57
|
-
|
|
58
47
|
case 'thinking': {
|
|
59
|
-
const text = ((event.data.text as string) ?? '').slice(0,
|
|
60
|
-
if (!text
|
|
61
|
-
return {
|
|
48
|
+
const text = ((event.data.text as string) ?? '').trim().slice(0, 100);
|
|
49
|
+
if (!text) return null;
|
|
50
|
+
return `${time} ${role} \uD83D\uDCAD ${text}`;
|
|
62
51
|
}
|
|
63
|
-
|
|
64
52
|
case 'tool:start': {
|
|
65
53
|
const name = (event.data.name as string) ?? 'tool';
|
|
66
|
-
const input = event.data.input;
|
|
54
|
+
const input = event.data.input as Record<string, unknown> | undefined;
|
|
67
55
|
let detail = '';
|
|
68
|
-
if (input
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
else if (
|
|
72
|
-
else if (inp.pattern) detail = ` ${String(inp.pattern)}`;
|
|
73
|
-
else detail = ` ${JSON.stringify(input).slice(0, 80)}`;
|
|
56
|
+
if (input) {
|
|
57
|
+
if (input.file_path) detail = ` ${String(input.file_path).slice(0, 60)}`;
|
|
58
|
+
else if (input.command) detail = ` ${String(input.command).slice(0, 60)}`;
|
|
59
|
+
else if (input.pattern) detail = ` ${String(input.pattern)}`;
|
|
74
60
|
}
|
|
75
|
-
return {
|
|
76
|
-
content: `\u2192 ${name}${detail}`,
|
|
77
|
-
contentColor: 'gray',
|
|
78
|
-
};
|
|
61
|
+
return `${time} ${role} \u2192 ${name}${detail}`;
|
|
79
62
|
}
|
|
80
|
-
|
|
81
|
-
case 'tool:result':
|
|
82
|
-
return {
|
|
83
|
-
content: `\u2190 ${(event.data.name as string) ?? 'tool'} done`,
|
|
84
|
-
contentColor: 'gray',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
63
|
case 'dispatch:start':
|
|
88
|
-
return {
|
|
89
|
-
content: `\u21D2 dispatch ${event.data.targetRole as string ?? ''}: ${(event.data.task as string)?.replace(/\u26D4[^\u26D4]*\u26D4[^"]*/g, '').trim().slice(0, 80) ?? ''}`,
|
|
90
|
-
contentColor: 'yellow',
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
case 'dispatch:done':
|
|
94
|
-
return {
|
|
95
|
-
content: `\u21D0 ${event.data.targetRole as string ?? ''} completed`,
|
|
96
|
-
contentColor: 'yellow',
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
case 'msg:awaiting_input': {
|
|
100
|
-
const question = (event.data.question as string) ?? '';
|
|
101
|
-
return {
|
|
102
|
-
content: question ? `? ${question.slice(0, 120)}` : '? Awaiting input...',
|
|
103
|
-
contentColor: 'yellow',
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Hidden (truly internal only)
|
|
108
|
-
case 'heartbeat:tick':
|
|
109
|
-
case 'heartbeat:skip':
|
|
110
|
-
case 'prompt:assembled':
|
|
111
|
-
case 'trace:response':
|
|
112
|
-
return null;
|
|
113
|
-
|
|
64
|
+
return `${time} ${role} \u21D2 dispatch ${event.data.targetRole as string ?? ''}`;
|
|
114
65
|
default:
|
|
115
66
|
return null;
|
|
116
67
|
}
|
|
117
68
|
}
|
|
118
69
|
|
|
119
70
|
const StreamViewInner: React.FC<StreamViewProps> = ({
|
|
120
|
-
events,
|
|
121
|
-
allRoleIds,
|
|
122
|
-
streamStatus,
|
|
123
|
-
waveId,
|
|
124
|
-
roleLabel,
|
|
71
|
+
events, allRoleIds, streamStatus, waveId, roleLabel,
|
|
125
72
|
}) => {
|
|
126
|
-
const
|
|
73
|
+
const termRows = process.stdout.rows || 40;
|
|
74
|
+
const maxVisible = Math.min(Math.max(5, termRows - 15), 20);
|
|
127
75
|
const visibleEvents = events.slice(-maxVisible);
|
|
128
|
-
|
|
129
76
|
const turnCount = events.filter(e => e.type === 'text' || e.type === 'tool:start').length;
|
|
130
77
|
|
|
131
78
|
const statusLabel = streamStatus === 'streaming' ? '\u25CF streaming'
|
|
@@ -133,39 +80,24 @@ const StreamViewInner: React.FC<StreamViewProps> = ({
|
|
|
133
80
|
: streamStatus === 'error' ? '\u2717 error'
|
|
134
81
|
: 'idle';
|
|
135
82
|
|
|
83
|
+
// Build single text block (1 yoga node instead of 90+)
|
|
84
|
+
const lines = visibleEvents
|
|
85
|
+
.map(e => eventToLine(e))
|
|
86
|
+
.filter(Boolean) as string[];
|
|
87
|
+
|
|
88
|
+
const content = lines.length > 0
|
|
89
|
+
? lines.join('\n')
|
|
90
|
+
: (waveId ? `Streaming... waiting for ${roleLabel !== 'All' ? roleLabel + ' ' : ''}events` : 'No active stream. Dispatch a wave to start.');
|
|
91
|
+
|
|
136
92
|
return (
|
|
137
|
-
<Box flexDirection="column" paddingX={1}
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
Stream ({roleLabel})
|
|
141
|
-
</Text>
|
|
93
|
+
<Box flexDirection="column" paddingX={1}>
|
|
94
|
+
<Text bold color="cyan">
|
|
95
|
+
Stream ({roleLabel}){' '}
|
|
142
96
|
<Text color={streamStatus === 'streaming' ? 'green' : 'gray'}>
|
|
143
97
|
{statusLabel} {turnCount > 0 ? `turn ${turnCount}` : ''}
|
|
144
98
|
</Text>
|
|
145
|
-
</
|
|
146
|
-
|
|
147
|
-
{visibleEvents.length === 0 && (
|
|
148
|
-
<Box marginTop={1}>
|
|
149
|
-
<Text color="gray" dimColor>
|
|
150
|
-
{waveId
|
|
151
|
-
? `Streaming... waiting for ${roleLabel !== 'All' ? roleLabel + ' ' : ''}events`
|
|
152
|
-
: 'No active stream. Dispatch a wave to start.'}
|
|
153
|
-
</Text>
|
|
154
|
-
</Box>
|
|
155
|
-
)}
|
|
156
|
-
|
|
157
|
-
{visibleEvents.map((event, i) => {
|
|
158
|
-
const rendered = renderEvent(event);
|
|
159
|
-
if (!rendered) return null;
|
|
160
|
-
const roleColor = getRoleColor(event.roleId, allRoleIds);
|
|
161
|
-
return (
|
|
162
|
-
<Box key={`${event.seq}-${i}`}>
|
|
163
|
-
<Text color="gray" dimColor>{formatTime(event.ts)} </Text>
|
|
164
|
-
<Text color={roleColor} bold>{event.roleId.padEnd(12)}</Text>
|
|
165
|
-
<Text color={rendered.contentColor} wrap="truncate">{rendered.content}</Text>
|
|
166
|
-
</Box>
|
|
167
|
-
);
|
|
168
|
-
})}
|
|
99
|
+
</Text>
|
|
100
|
+
<Text color="white" wrap="truncate">{content}</Text>
|
|
169
101
|
</Box>
|
|
170
102
|
);
|
|
171
103
|
};
|