tycono 0.1.96-beta.14 → 0.1.96-beta.16
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/tui/app.tsx +3 -2
- package/src/tui/components/CommandMode.tsx +99 -50
package/package.json
CHANGED
package/src/tui/app.tsx
CHANGED
|
@@ -119,6 +119,7 @@ export const App: React.FC = () => {
|
|
|
119
119
|
api.refresh();
|
|
120
120
|
},
|
|
121
121
|
onStopped: () => {
|
|
122
|
+
setWaveId(null);
|
|
122
123
|
setWaveStatus('idle');
|
|
123
124
|
api.refresh();
|
|
124
125
|
},
|
|
@@ -134,10 +135,10 @@ export const App: React.FC = () => {
|
|
|
134
135
|
|
|
135
136
|
switch (result.type) {
|
|
136
137
|
case 'wave_started':
|
|
137
|
-
|
|
138
|
+
// Don't show wave ID noise — supervisor handles it
|
|
138
139
|
break;
|
|
139
140
|
case 'directive_sent':
|
|
140
|
-
|
|
141
|
+
// Silently sent — supervisor will respond
|
|
141
142
|
break;
|
|
142
143
|
case 'stopped':
|
|
143
144
|
addSystemMessage(`\u26A1 ${result.message}`, 'red');
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CommandMode —
|
|
2
|
+
* CommandMode — chat-first mode
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* msg:done → "QA: done (12 turns)"
|
|
9
|
-
* msg:error → "FE: TypeError at line 42"
|
|
10
|
-
* thinking, heartbeat, trace → hidden
|
|
4
|
+
* User = CEO. Supervisor (ceo role) = user's AI proxy.
|
|
5
|
+
* - Supervisor responses: shown directly (no prefix), like a conversation
|
|
6
|
+
* - Team activity: indented with roleId, concise
|
|
7
|
+
* - System prompts, internal noise: filtered out
|
|
11
8
|
*/
|
|
12
9
|
|
|
13
10
|
import React, { useState, useCallback } from 'react';
|
|
@@ -16,7 +13,8 @@ import TextInput from 'ink-text-input';
|
|
|
16
13
|
import type { SSEEvent } from '../api';
|
|
17
14
|
import { getRoleColor } from '../theme';
|
|
18
15
|
|
|
19
|
-
const MAX_STREAM_LINES =
|
|
16
|
+
const MAX_STREAM_LINES = 30;
|
|
17
|
+
const SUPERVISOR_ROLE = 'ceo';
|
|
20
18
|
|
|
21
19
|
export interface StreamLine {
|
|
22
20
|
id: number;
|
|
@@ -24,6 +22,7 @@ export interface StreamLine {
|
|
|
24
22
|
color: string;
|
|
25
23
|
prefix?: string;
|
|
26
24
|
prefixColor?: string;
|
|
25
|
+
indent?: boolean;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
interface CommandModeProps {
|
|
@@ -35,21 +34,70 @@ interface CommandModeProps {
|
|
|
35
34
|
|
|
36
35
|
let lineCounter = 0;
|
|
37
36
|
|
|
38
|
-
/**
|
|
37
|
+
/** Filter out system prompt noise from text */
|
|
38
|
+
function isSystemNoise(text: string): boolean {
|
|
39
|
+
const t = text.trim();
|
|
40
|
+
if (!t) return true;
|
|
41
|
+
// System prompt fragments
|
|
42
|
+
if (t.startsWith('## Your Role')) return true;
|
|
43
|
+
if (t.startsWith('You are')) return true;
|
|
44
|
+
if (t.startsWith('[CEO Supervisor]')) return true;
|
|
45
|
+
if (t.startsWith('[Question from')) return true;
|
|
46
|
+
if (t.includes('⛔ AKB Rule')) return true;
|
|
47
|
+
if (t.includes('⛔ Read the')) return true;
|
|
48
|
+
if (t.startsWith('⛔')) return true;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Convert SSE event to stream lines */
|
|
39
53
|
export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLine | null {
|
|
54
|
+
const isSupervisor = event.roleId === SUPERVISOR_ROLE;
|
|
40
55
|
const roleColor = getRoleColor(event.roleId, allRoleIds);
|
|
41
|
-
const roleName = event.roleId;
|
|
42
56
|
|
|
43
57
|
switch (event.type) {
|
|
58
|
+
case 'text': {
|
|
59
|
+
const text = ((event.data.text as string) ?? '');
|
|
60
|
+
if (isSystemNoise(text)) return null;
|
|
61
|
+
|
|
62
|
+
if (isSupervisor) {
|
|
63
|
+
// Supervisor text → direct response (no prefix, generous length)
|
|
64
|
+
return {
|
|
65
|
+
id: ++lineCounter,
|
|
66
|
+
text: text.slice(0, 200),
|
|
67
|
+
color: 'white',
|
|
68
|
+
};
|
|
69
|
+
} else {
|
|
70
|
+
// Team text → indented with role prefix, concise
|
|
71
|
+
return {
|
|
72
|
+
id: ++lineCounter,
|
|
73
|
+
prefix: event.roleId,
|
|
74
|
+
prefixColor: roleColor,
|
|
75
|
+
text: text.slice(0, 80),
|
|
76
|
+
color: 'white',
|
|
77
|
+
indent: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
44
82
|
case 'dispatch:start': {
|
|
45
83
|
const target = (event.data.targetRole as string) ?? '';
|
|
46
|
-
const task = ((event.data.task as string) ?? '')
|
|
84
|
+
const task = ((event.data.task as string) ?? '');
|
|
85
|
+
// Filter out system prompt from task display
|
|
86
|
+
const cleanTask = task.replace(/⛔[^⛔]*⛔[^"]*/g, '').trim().slice(0, 50);
|
|
87
|
+
if (isSupervisor) {
|
|
88
|
+
return {
|
|
89
|
+
id: ++lineCounter,
|
|
90
|
+
text: `→ ${target} 배정${cleanTask ? ': ' + cleanTask : ''}`,
|
|
91
|
+
color: 'yellow',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
47
94
|
return {
|
|
48
95
|
id: ++lineCounter,
|
|
49
|
-
prefix:
|
|
96
|
+
prefix: event.roleId,
|
|
50
97
|
prefixColor: roleColor,
|
|
51
|
-
text:
|
|
98
|
+
text: `→ ${target} 배정`,
|
|
52
99
|
color: 'yellow',
|
|
100
|
+
indent: true,
|
|
53
101
|
};
|
|
54
102
|
}
|
|
55
103
|
|
|
@@ -57,22 +105,11 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
|
|
|
57
105
|
const target = (event.data.targetRole as string) ?? '';
|
|
58
106
|
return {
|
|
59
107
|
id: ++lineCounter,
|
|
60
|
-
prefix:
|
|
108
|
+
prefix: event.roleId,
|
|
61
109
|
prefixColor: roleColor,
|
|
62
|
-
text:
|
|
110
|
+
text: `← ${target} 완료`,
|
|
63
111
|
color: 'yellow',
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
case 'text': {
|
|
68
|
-
const text = ((event.data.text as string) ?? '').slice(0, 60);
|
|
69
|
-
if (!text.trim()) return null;
|
|
70
|
-
return {
|
|
71
|
-
id: ++lineCounter,
|
|
72
|
-
prefix: roleName,
|
|
73
|
-
prefixColor: roleColor,
|
|
74
|
-
text,
|
|
75
|
-
color: 'white',
|
|
112
|
+
indent: !isSupervisor,
|
|
76
113
|
};
|
|
77
114
|
}
|
|
78
115
|
|
|
@@ -85,60 +122,73 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
|
|
|
85
122
|
if (inp.file_path) detail = ` ${String(inp.file_path).split('/').pop()}`;
|
|
86
123
|
else if (inp.command) detail = ` ${String(inp.command).slice(0, 40)}`;
|
|
87
124
|
}
|
|
125
|
+
|
|
126
|
+
if (isSupervisor) {
|
|
127
|
+
// Supervisor tool use → subtle
|
|
128
|
+
return {
|
|
129
|
+
id: ++lineCounter,
|
|
130
|
+
text: ` → ${toolName}${detail}`,
|
|
131
|
+
color: 'gray',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
88
134
|
return {
|
|
89
135
|
id: ++lineCounter,
|
|
90
|
-
prefix:
|
|
136
|
+
prefix: event.roleId,
|
|
91
137
|
prefixColor: roleColor,
|
|
92
|
-
text:
|
|
138
|
+
text: `→ ${toolName}${detail}`,
|
|
93
139
|
color: 'gray',
|
|
140
|
+
indent: true,
|
|
94
141
|
};
|
|
95
142
|
}
|
|
96
143
|
|
|
97
144
|
case 'msg:start': {
|
|
98
|
-
|
|
145
|
+
if (isSupervisor) return null; // Hide supervisor start (noise)
|
|
146
|
+
const task = ((event.data.task as string) ?? '');
|
|
147
|
+
const cleanTask = task.replace(/⛔[^⛔]*⛔[^"]*/g, '').trim().slice(0, 40);
|
|
99
148
|
return {
|
|
100
149
|
id: ++lineCounter,
|
|
101
|
-
prefix:
|
|
150
|
+
prefix: event.roleId,
|
|
102
151
|
prefixColor: roleColor,
|
|
103
|
-
text:
|
|
152
|
+
text: `▶ ${cleanTask || 'started'}`,
|
|
104
153
|
color: 'green',
|
|
154
|
+
indent: true,
|
|
105
155
|
};
|
|
106
156
|
}
|
|
107
157
|
|
|
108
158
|
case 'msg:done': {
|
|
109
159
|
const turns = event.data.turns as number | undefined;
|
|
110
|
-
|
|
160
|
+
if (isSupervisor) return null; // Hide supervisor done
|
|
111
161
|
return {
|
|
112
162
|
id: ++lineCounter,
|
|
113
|
-
prefix:
|
|
163
|
+
prefix: event.roleId,
|
|
114
164
|
prefixColor: roleColor,
|
|
115
|
-
text:
|
|
165
|
+
text: `✓ done${turns ? ` (${turns} turns)` : ''}`,
|
|
116
166
|
color: 'green',
|
|
167
|
+
indent: true,
|
|
117
168
|
};
|
|
118
169
|
}
|
|
119
170
|
|
|
120
171
|
case 'msg:error': {
|
|
172
|
+
if (isSupervisor) return null; // Supervisor errors handled by system messages
|
|
121
173
|
const error = ((event.data.error as string) ?? '').slice(0, 60);
|
|
122
174
|
return {
|
|
123
175
|
id: ++lineCounter,
|
|
124
|
-
prefix:
|
|
176
|
+
prefix: event.roleId,
|
|
125
177
|
prefixColor: roleColor,
|
|
126
|
-
text:
|
|
178
|
+
text: `✗ ${error}`,
|
|
127
179
|
color: 'red',
|
|
180
|
+
indent: true,
|
|
128
181
|
};
|
|
129
182
|
}
|
|
130
183
|
|
|
131
|
-
case 'msg:awaiting_input':
|
|
184
|
+
case 'msg:awaiting_input':
|
|
132
185
|
return {
|
|
133
186
|
id: ++lineCounter,
|
|
134
|
-
|
|
135
|
-
prefixColor: roleColor,
|
|
136
|
-
text: '? Awaiting input...',
|
|
187
|
+
text: isSupervisor ? '...' : ` ${event.roleId}: waiting`,
|
|
137
188
|
color: 'yellow',
|
|
138
189
|
};
|
|
139
|
-
}
|
|
140
190
|
|
|
141
|
-
// Hidden
|
|
191
|
+
// Hidden
|
|
142
192
|
case 'thinking':
|
|
143
193
|
case 'heartbeat:tick':
|
|
144
194
|
case 'heartbeat:skip':
|
|
@@ -191,12 +241,11 @@ export const CommandMode: React.FC<CommandModeProps> = ({
|
|
|
191
241
|
)}
|
|
192
242
|
{allLines.map((line) => (
|
|
193
243
|
<Box key={line.id}>
|
|
244
|
+
{line.indent && <Text> </Text>}
|
|
194
245
|
{line.prefix && (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
</Text>
|
|
199
|
-
</>
|
|
246
|
+
<Text color={line.prefixColor} bold>
|
|
247
|
+
{(line.prefix).padEnd(12)}
|
|
248
|
+
</Text>
|
|
200
249
|
)}
|
|
201
250
|
<Text color={line.color}>{line.text}</Text>
|
|
202
251
|
</Box>
|
|
@@ -208,7 +257,7 @@ export const CommandMode: React.FC<CommandModeProps> = ({
|
|
|
208
257
|
<Text color="gray">{'─'.repeat(process.stdout.columns || 70)}</Text>
|
|
209
258
|
</Box>
|
|
210
259
|
|
|
211
|
-
{/*
|
|
260
|
+
{/* Input */}
|
|
212
261
|
<Box paddingX={1} justifyContent="space-between">
|
|
213
262
|
<Box>
|
|
214
263
|
<Text color="yellow" bold>> </Text>
|