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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.1.96-beta.14",
3
+ "version": "0.1.96-beta.16",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
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
- addSystemMessage(`\u26A1 ${result.message}`, 'yellow');
138
+ // Don't show wave ID noise — supervisor handles it
138
139
  break;
139
140
  case 'directive_sent':
140
- addSystemMessage(`\u26A1 ${result.message}`, 'yellow');
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 — default mode: stream summary flowing above, command input below
2
+ * CommandMode — chat-first mode
3
3
  *
4
- * SSE events are compressed into one-line summaries:
5
- * dispatch:start "CEO -> CTO Su (supervising...)"
6
- * text → "FE: Canvas game loop implementation..."
7
- * tool:start → "FE: -> edit index.html +87 lines"
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 = 20;
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
- /** Convert SSE event to a one-line summary. Returns null if event should be hidden. */
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) ?? '').slice(0, 50);
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: roleName,
96
+ prefix: event.roleId,
50
97
  prefixColor: roleColor,
51
- text: `\u2192 ${target} (${task || 'dispatching...'})`,
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: roleName,
108
+ prefix: event.roleId,
61
109
  prefixColor: roleColor,
62
- text: `\u2190 ${target} completed`,
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: roleName,
136
+ prefix: event.roleId,
91
137
  prefixColor: roleColor,
92
- text: `\u2192 ${toolName}${detail}`,
138
+ text: `→ ${toolName}${detail}`,
93
139
  color: 'gray',
140
+ indent: true,
94
141
  };
95
142
  }
96
143
 
97
144
  case 'msg:start': {
98
- const task = ((event.data.task as string) ?? '').slice(0, 50);
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: roleName,
150
+ prefix: event.roleId,
102
151
  prefixColor: roleColor,
103
- text: `\u25B6 ${task || 'started'}`,
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
- const turnLabel = turns ? ` (${turns} turns)` : '';
160
+ if (isSupervisor) return null; // Hide supervisor done
111
161
  return {
112
162
  id: ++lineCounter,
113
- prefix: roleName,
163
+ prefix: event.roleId,
114
164
  prefixColor: roleColor,
115
- text: `\u2713 done${turnLabel}`,
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: roleName,
176
+ prefix: event.roleId,
125
177
  prefixColor: roleColor,
126
- text: `\u2717 ${error}`,
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
- prefix: roleName,
135
- prefixColor: roleColor,
136
- text: '? Awaiting input...',
187
+ text: isSupervisor ? '...' : ` ${event.roleId}: waiting`,
137
188
  color: 'yellow',
138
189
  };
139
- }
140
190
 
141
- // Hidden events
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
- <Text color={line.prefixColor} bold>
197
- {(line.prefix + ':').padEnd(14)}
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
- {/* Command input */}
260
+ {/* Input */}
212
261
  <Box paddingX={1} justifyContent="space-between">
213
262
  <Box>
214
263
  <Text color="yellow" bold>&gt; </Text>