tycono 0.1.96-beta.13 → 0.1.96-beta.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 +1 -1
- package/src/tui/app.tsx +19 -26
- package/src/tui/components/CommandMode.tsx +100 -52
- package/src/tui/components/StatusBar.tsx +27 -22
- package/src/tui/hooks/useCommand.ts +75 -118
package/package.json
CHANGED
package/src/tui/app.tsx
CHANGED
|
@@ -134,10 +134,10 @@ export const App: React.FC = () => {
|
|
|
134
134
|
|
|
135
135
|
switch (result.type) {
|
|
136
136
|
case 'wave_started':
|
|
137
|
-
|
|
137
|
+
// Don't show wave ID noise — supervisor handles it
|
|
138
138
|
break;
|
|
139
139
|
case 'directive_sent':
|
|
140
|
-
|
|
140
|
+
// Silently sent — supervisor will respond
|
|
141
141
|
break;
|
|
142
142
|
case 'stopped':
|
|
143
143
|
addSystemMessage(`\u26A1 ${result.message}`, 'red');
|
|
@@ -146,16 +146,14 @@ export const App: React.FC = () => {
|
|
|
146
146
|
addSystemMessage(result.message, 'red');
|
|
147
147
|
break;
|
|
148
148
|
case 'help':
|
|
149
|
-
addSystemMessage('
|
|
150
|
-
addSystemMessage('
|
|
151
|
-
addSystemMessage('
|
|
152
|
-
addSystemMessage('
|
|
153
|
-
addSystemMessage('
|
|
154
|
-
addSystemMessage('
|
|
155
|
-
addSystemMessage('
|
|
156
|
-
addSystemMessage('
|
|
157
|
-
addSystemMessage(' help Show this help', 'white');
|
|
158
|
-
addSystemMessage(' quit Exit TUI', 'white');
|
|
149
|
+
addSystemMessage('Type naturally to talk to your AI team.', 'cyan');
|
|
150
|
+
addSystemMessage('Commands (/ prefix):', 'cyan');
|
|
151
|
+
addSystemMessage(' /stop Stop all executions', 'white');
|
|
152
|
+
addSystemMessage(' /status Show current status', 'white');
|
|
153
|
+
addSystemMessage(' /assign <role> <task> Assign task to role', 'white');
|
|
154
|
+
addSystemMessage(' /roles Org tree (Panel Mode)', 'white');
|
|
155
|
+
addSystemMessage(' /help Show this help', 'white');
|
|
156
|
+
addSystemMessage(' /quit Exit TUI', 'white');
|
|
159
157
|
addSystemMessage('Keys: [Tab] panel [Esc] back [Ctrl+C] stop/quit', 'gray');
|
|
160
158
|
break;
|
|
161
159
|
case 'info':
|
|
@@ -231,20 +229,6 @@ export const App: React.FC = () => {
|
|
|
231
229
|
|
|
232
230
|
return (
|
|
233
231
|
<Box flexDirection="column" height={termHeight}>
|
|
234
|
-
{/* Status Bar — always shown */}
|
|
235
|
-
<StatusBar
|
|
236
|
-
companyName={api.company?.name ?? 'Loading...'}
|
|
237
|
-
waveId={effectiveWaveId}
|
|
238
|
-
waveStatus={derivedWaveStatus}
|
|
239
|
-
activeCount={activeCount}
|
|
240
|
-
totalCost={0}
|
|
241
|
-
/>
|
|
242
|
-
|
|
243
|
-
{/* Separator */}
|
|
244
|
-
<Box width="100%">
|
|
245
|
-
<Text color="gray">{'─'.repeat(process.stdout.columns || 70)}</Text>
|
|
246
|
-
</Box>
|
|
247
|
-
|
|
248
232
|
{/* Mode content — fill remaining height */}
|
|
249
233
|
<Box flexGrow={1} flexDirection="column">
|
|
250
234
|
{mode === 'command' ? (
|
|
@@ -278,6 +262,15 @@ export const App: React.FC = () => {
|
|
|
278
262
|
/>
|
|
279
263
|
)}
|
|
280
264
|
</Box>
|
|
265
|
+
|
|
266
|
+
{/* Status Bar — bottom (Claude Code style) */}
|
|
267
|
+
<StatusBar
|
|
268
|
+
companyName={api.company?.name ?? 'Loading...'}
|
|
269
|
+
waveId={effectiveWaveId}
|
|
270
|
+
waveStatus={derivedWaveStatus}
|
|
271
|
+
activeCount={activeCount}
|
|
272
|
+
totalCost={0}
|
|
273
|
+
/>
|
|
281
274
|
</Box>
|
|
282
275
|
);
|
|
283
276
|
};
|
|
@@ -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,35 +122,49 @@ 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
|
|
|
@@ -121,24 +172,22 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
|
|
|
121
172
|
const error = ((event.data.error as string) ?? '').slice(0, 60);
|
|
122
173
|
return {
|
|
123
174
|
id: ++lineCounter,
|
|
124
|
-
prefix:
|
|
175
|
+
prefix: event.roleId,
|
|
125
176
|
prefixColor: roleColor,
|
|
126
|
-
text:
|
|
177
|
+
text: `✗ ${error}`,
|
|
127
178
|
color: 'red',
|
|
179
|
+
indent: !isSupervisor,
|
|
128
180
|
};
|
|
129
181
|
}
|
|
130
182
|
|
|
131
|
-
case 'msg:awaiting_input':
|
|
183
|
+
case 'msg:awaiting_input':
|
|
132
184
|
return {
|
|
133
185
|
id: ++lineCounter,
|
|
134
|
-
|
|
135
|
-
prefixColor: roleColor,
|
|
136
|
-
text: '? Awaiting input...',
|
|
186
|
+
text: isSupervisor ? '...' : ` ${event.roleId}: waiting`,
|
|
137
187
|
color: 'yellow',
|
|
138
188
|
};
|
|
139
|
-
}
|
|
140
189
|
|
|
141
|
-
// Hidden
|
|
190
|
+
// Hidden
|
|
142
191
|
case 'thinking':
|
|
143
192
|
case 'heartbeat:tick':
|
|
144
193
|
case 'heartbeat:skip':
|
|
@@ -185,18 +234,17 @@ export const CommandMode: React.FC<CommandModeProps> = ({
|
|
|
185
234
|
{allLines.length === 0 && (
|
|
186
235
|
<Box marginTop={1}>
|
|
187
236
|
<Text color="gray" dimColor>
|
|
188
|
-
|
|
237
|
+
Type a message to your AI team, or /help for commands.
|
|
189
238
|
</Text>
|
|
190
239
|
</Box>
|
|
191
240
|
)}
|
|
192
241
|
{allLines.map((line) => (
|
|
193
242
|
<Box key={line.id}>
|
|
243
|
+
{line.indent && <Text> </Text>}
|
|
194
244
|
{line.prefix && (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
</Text>
|
|
199
|
-
</>
|
|
245
|
+
<Text color={line.prefixColor} bold>
|
|
246
|
+
{(line.prefix).padEnd(12)}
|
|
247
|
+
</Text>
|
|
200
248
|
)}
|
|
201
249
|
<Text color={line.color}>{line.text}</Text>
|
|
202
250
|
</Box>
|
|
@@ -208,10 +256,10 @@ export const CommandMode: React.FC<CommandModeProps> = ({
|
|
|
208
256
|
<Text color="gray">{'─'.repeat(process.stdout.columns || 70)}</Text>
|
|
209
257
|
</Box>
|
|
210
258
|
|
|
211
|
-
{/*
|
|
259
|
+
{/* Input */}
|
|
212
260
|
<Box paddingX={1} justifyContent="space-between">
|
|
213
261
|
<Box>
|
|
214
|
-
<Text color="
|
|
262
|
+
<Text color="yellow" bold>> </Text>
|
|
215
263
|
<TextInput
|
|
216
264
|
value={input}
|
|
217
265
|
onChange={setInput}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* StatusBar —
|
|
2
|
+
* StatusBar — bottom bar (Claude Code style)
|
|
3
|
+
* Shows: company name, wave status, active roles, cost
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import React from 'react';
|
|
@@ -21,29 +22,33 @@ export const StatusBar: React.FC<StatusBarProps> = ({
|
|
|
21
22
|
totalCost,
|
|
22
23
|
}) => {
|
|
23
24
|
const waveLabel = waveId
|
|
24
|
-
? `Wave ${waveId.replace('wave-', '#')}
|
|
25
|
-
: '
|
|
25
|
+
? `Wave ${waveId.replace('wave-', '#')}`
|
|
26
|
+
: '';
|
|
27
|
+
const statusDot = waveStatus === 'running' ? ' ●'
|
|
28
|
+
: waveStatus === 'done' ? ' ✓'
|
|
29
|
+
: '';
|
|
26
30
|
|
|
27
31
|
return (
|
|
28
|
-
<Box
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</
|
|
32
|
+
<Box width="100%" paddingX={1}>
|
|
33
|
+
<Text color="cyan" bold>Tycono</Text>
|
|
34
|
+
<Text color="gray"> | </Text>
|
|
35
|
+
<Text color="white">{companyName}</Text>
|
|
36
|
+
{waveLabel && (
|
|
37
|
+
<>
|
|
38
|
+
<Text color="gray"> | </Text>
|
|
39
|
+
<Text color={waveStatus === 'running' ? 'green' : 'gray'}>
|
|
40
|
+
{waveLabel}{statusDot}
|
|
41
|
+
</Text>
|
|
42
|
+
</>
|
|
43
|
+
)}
|
|
44
|
+
{activeCount > 0 && (
|
|
45
|
+
<>
|
|
46
|
+
<Text color="gray"> | </Text>
|
|
47
|
+
<Text color="yellow">{activeCount} active</Text>
|
|
48
|
+
</>
|
|
49
|
+
)}
|
|
50
|
+
<Text color="gray"> | </Text>
|
|
51
|
+
<Text color="green">${totalCost.toFixed(2)}</Text>
|
|
47
52
|
</Box>
|
|
48
53
|
);
|
|
49
54
|
};
|
|
@@ -1,54 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* useCommand —
|
|
2
|
+
* useCommand — input handler for TUI v2
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* help — show help
|
|
13
|
-
* quit — exit
|
|
4
|
+
* Default: natural language → wave dispatch or directive (if wave active)
|
|
5
|
+
* Commands (/ prefix):
|
|
6
|
+
* /stop — abort all active executions
|
|
7
|
+
* /status — show current wave + session status
|
|
8
|
+
* /assign <role> <task> — assign task to specific role
|
|
9
|
+
* /roles — show org tree (Panel Mode)
|
|
10
|
+
* /help — show help
|
|
11
|
+
* /quit — exit
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
import { useCallback } from 'react';
|
|
17
15
|
import { dispatchWave, sendDirective, fetchJson } from '../api';
|
|
18
16
|
|
|
19
|
-
export interface ParsedCommand {
|
|
20
|
-
cmd: string;
|
|
21
|
-
args: string;
|
|
22
|
-
flags: Record<string, boolean>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
17
|
export interface CommandResult {
|
|
26
18
|
type: 'success' | 'error' | 'info' | 'wave_started' | 'directive_sent' | 'stopped' | 'quit' | 'help' | 'panel';
|
|
27
19
|
message: string;
|
|
28
20
|
waveId?: string;
|
|
29
21
|
}
|
|
30
22
|
|
|
31
|
-
export function parseCommand(input: string): ParsedCommand {
|
|
32
|
-
const trimmed = input.trim();
|
|
33
|
-
if (!trimmed) return { cmd: '', args: '', flags: {} };
|
|
34
|
-
|
|
35
|
-
const parts = trimmed.split(/\s+/);
|
|
36
|
-
const cmd = parts[0].toLowerCase();
|
|
37
|
-
const flags: Record<string, boolean> = {};
|
|
38
|
-
const argParts: string[] = [];
|
|
39
|
-
|
|
40
|
-
for (let i = 1; i < parts.length; i++) {
|
|
41
|
-
if (parts[i].startsWith('--')) {
|
|
42
|
-
const flagName = parts[i].slice(2);
|
|
43
|
-
if (flagName) flags[flagName] = true;
|
|
44
|
-
} else {
|
|
45
|
-
argParts.push(parts[i]);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { cmd, args: argParts.join(' '), flags };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
23
|
async function postAbortAll(): Promise<void> {
|
|
53
24
|
try {
|
|
54
25
|
const status = await fetchJson<{
|
|
@@ -88,97 +59,83 @@ export function useCommand(options: UseCommandOptions) {
|
|
|
88
59
|
const { activeWaveId, onWaveStarted, onStopped, onQuit, onShowPanel } = options;
|
|
89
60
|
|
|
90
61
|
const execute = useCallback(async (input: string): Promise<CommandResult> => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
type: '
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
} catch (err) {
|
|
110
|
-
return { type: 'error', message: `Wave failed: ${err instanceof Error ? err.message : 'unknown'}` };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
case 'directive': {
|
|
115
|
-
if (!args) {
|
|
116
|
-
return { type: 'error', message: 'Usage: directive <text>' };
|
|
117
|
-
}
|
|
118
|
-
if (!activeWaveId) {
|
|
119
|
-
return { type: 'error', message: 'No active wave. Start one with: wave <directive>' };
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
await sendDirective(activeWaveId, args);
|
|
123
|
-
return { type: 'directive_sent', message: `Directive sent to ${activeWaveId.replace('wave-', 'Wave #')}` };
|
|
124
|
-
} catch (err) {
|
|
125
|
-
return { type: 'error', message: `Directive failed: ${err instanceof Error ? err.message : 'unknown'}` };
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
case 'stop': {
|
|
130
|
-
try {
|
|
131
|
-
await postAbortAll();
|
|
132
|
-
onStopped();
|
|
133
|
-
return { type: 'stopped', message: 'All active executions stopped.' };
|
|
134
|
-
} catch (err) {
|
|
135
|
-
return { type: 'error', message: `Stop failed: ${err instanceof Error ? err.message : 'unknown'}` };
|
|
62
|
+
const trimmed = input.trim();
|
|
63
|
+
if (!trimmed) return { type: 'info', message: '' };
|
|
64
|
+
|
|
65
|
+
// Slash commands: /stop, /status, /help, /quit, /roles, /assign
|
|
66
|
+
if (trimmed.startsWith('/')) {
|
|
67
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
68
|
+
const cmd = parts[0].toLowerCase();
|
|
69
|
+
const args = parts.slice(1).join(' ');
|
|
70
|
+
|
|
71
|
+
switch (cmd) {
|
|
72
|
+
case 'stop': {
|
|
73
|
+
try {
|
|
74
|
+
await postAbortAll();
|
|
75
|
+
onStopped();
|
|
76
|
+
return { type: 'stopped', message: 'All active executions stopped.' };
|
|
77
|
+
} catch (err) {
|
|
78
|
+
return { type: 'error', message: `Stop failed: ${err instanceof Error ? err.message : 'unknown'}` };
|
|
79
|
+
}
|
|
136
80
|
}
|
|
137
|
-
}
|
|
138
81
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
82
|
+
case 'status':
|
|
83
|
+
return { type: 'info', message: '__status__' };
|
|
142
84
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
85
|
+
case 'assign': {
|
|
86
|
+
const spaceIdx = args.indexOf(' ');
|
|
87
|
+
if (spaceIdx === -1 || !args) {
|
|
88
|
+
return { type: 'error', message: 'Usage: /assign <role> <task>' };
|
|
89
|
+
}
|
|
90
|
+
const roleId = args.slice(0, spaceIdx);
|
|
91
|
+
const task = args.slice(spaceIdx + 1);
|
|
92
|
+
try {
|
|
93
|
+
const result = await postAssign(roleId, task);
|
|
94
|
+
if (result.waveId) {
|
|
95
|
+
onWaveStarted(result.waveId);
|
|
96
|
+
}
|
|
97
|
+
return { type: 'success', message: `Task assigned to ${roleId}`, waveId: result.waveId };
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return { type: 'error', message: `Assign failed: ${err instanceof Error ? err.message : 'unknown'}` };
|
|
154
100
|
}
|
|
155
|
-
return { type: 'success', message: `Task assigned to ${roleId}`, waveId: result.waveId };
|
|
156
|
-
} catch (err) {
|
|
157
|
-
return { type: 'error', message: `Assign failed: ${err instanceof Error ? err.message : 'unknown'}` };
|
|
158
101
|
}
|
|
159
|
-
}
|
|
160
102
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
103
|
+
case 'roles':
|
|
104
|
+
onShowPanel();
|
|
105
|
+
return { type: 'panel', message: '' };
|
|
164
106
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return { type: 'panel', message: 'Switching to Panel Mode...' };
|
|
168
|
-
}
|
|
107
|
+
case 'help':
|
|
108
|
+
return { type: 'help', message: '__help__' };
|
|
169
109
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
110
|
+
case 'quit':
|
|
111
|
+
case 'exit':
|
|
112
|
+
onQuit();
|
|
113
|
+
return { type: 'quit', message: 'Goodbye!' };
|
|
173
114
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
onQuit();
|
|
177
|
-
return { type: 'quit', message: 'Goodbye!' };
|
|
115
|
+
default:
|
|
116
|
+
return { type: 'error', message: `Unknown command: /${cmd}. Type /help for commands.` };
|
|
178
117
|
}
|
|
118
|
+
}
|
|
179
119
|
|
|
180
|
-
|
|
181
|
-
|
|
120
|
+
// Default: natural language → directive (if wave active) or new wave
|
|
121
|
+
if (activeWaveId) {
|
|
122
|
+
try {
|
|
123
|
+
await sendDirective(activeWaveId, trimmed);
|
|
124
|
+
return { type: 'directive_sent', message: `Directive sent` };
|
|
125
|
+
} catch (err) {
|
|
126
|
+
return { type: 'error', message: `Failed: ${err instanceof Error ? err.message : 'unknown'}` };
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
try {
|
|
130
|
+
const result = await dispatchWave(trimmed);
|
|
131
|
+
onWaveStarted(result.waveId);
|
|
132
|
+
return {
|
|
133
|
+
type: 'wave_started',
|
|
134
|
+
message: `Wave ${result.waveId.replace('wave-', '#')} started`,
|
|
135
|
+
waveId: result.waveId,
|
|
136
|
+
};
|
|
137
|
+
} catch (err) {
|
|
138
|
+
return { type: 'error', message: `Wave failed: ${err instanceof Error ? err.message : 'unknown'}` };
|
|
182
139
|
}
|
|
183
140
|
}
|
|
184
141
|
}, [activeWaveId, onWaveStarted, onStopped, onQuit, onShowPanel]);
|