agentk8 2.2.6 → 2.3.1
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/dist/cli.js +1 -1
- package/dist/components/App.js +322 -62
- package/dist/components/ChatMessage.js +93 -1
- package/dist/components/Confirmation.d.ts +13 -0
- package/dist/components/Confirmation.js +39 -0
- package/dist/components/QuestionWizard.d.ts +21 -0
- package/dist/components/QuestionWizard.js +152 -0
- package/dist/components/StatusBar.d.ts +6 -0
- package/dist/components/StatusBar.js +31 -7
- package/dist/components/WelcomeBox.js +17 -11
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +1 -0
- package/dist/lib/claude.js +59 -22
- package/dist/lib/council.d.ts +61 -0
- package/dist/lib/council.js +189 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
package/dist/components/App.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
3
|
import { Box, Text, useApp, useInput, Static } from 'ink';
|
|
4
4
|
import { WelcomeBox } from './WelcomeBox.js';
|
|
5
5
|
import { ChatMessage } from './ChatMessage.js';
|
|
@@ -7,9 +7,19 @@ import { Input } from './Input.js';
|
|
|
7
7
|
import { StatusBar } from './StatusBar.js';
|
|
8
8
|
import { ThinkingIndicator } from './ThinkingIndicator.js';
|
|
9
9
|
import { runClaude } from '../lib/claude.js';
|
|
10
|
+
import { runCouncil, checkCouncilAvailable, getAvailableModels } from '../lib/council.js';
|
|
11
|
+
import { Confirmation } from './Confirmation.js';
|
|
12
|
+
import { QuestionWizard } from './QuestionWizard.js';
|
|
10
13
|
export const App = ({ mode, version }) => {
|
|
11
14
|
const { exit } = useApp();
|
|
12
|
-
|
|
15
|
+
// Initialize with welcome message as permanent first item
|
|
16
|
+
const [messages, setMessages] = useState([{
|
|
17
|
+
id: 'welcome',
|
|
18
|
+
role: 'system',
|
|
19
|
+
content: '',
|
|
20
|
+
timestamp: new Date(),
|
|
21
|
+
isWelcome: true,
|
|
22
|
+
}]);
|
|
13
23
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
14
24
|
const [processingStartTime, setProcessingStartTime] = useState(null);
|
|
15
25
|
const [totalTokens, setTotalTokens] = useState(0);
|
|
@@ -18,10 +28,71 @@ export const App = ({ mode, version }) => {
|
|
|
18
28
|
const [executionMode, setExecutionMode] = useState('plan');
|
|
19
29
|
const [activeAgent, setActiveAgent] = useState(undefined);
|
|
20
30
|
const [completedAgents, setCompletedAgents] = useState([]);
|
|
21
|
-
const [
|
|
22
|
-
const [awaitingApproval, setAwaitingApproval] = useState(false);
|
|
31
|
+
const [confirmationState, setConfirmationState] = useState(null);
|
|
23
32
|
const [autoAccept, setAutoAccept] = useState(false);
|
|
24
|
-
const [
|
|
33
|
+
const [councilMode, setCouncilMode] = useState('off');
|
|
34
|
+
const [councilAvailable, setCouncilAvailable] = useState(false);
|
|
35
|
+
const [availableModels, setAvailableModels] = useState({});
|
|
36
|
+
const [councilStage, setCouncilStage] = useState(null);
|
|
37
|
+
const [lastEscapeTime, setLastEscapeTime] = useState(0);
|
|
38
|
+
const [showExitHint, setShowExitHint] = useState(false);
|
|
39
|
+
const [questionWizardState, setQuestionWizardState] = useState(null);
|
|
40
|
+
// Clean orchestrator internal tags from response
|
|
41
|
+
const cleanOrchestratorTags = (response) => {
|
|
42
|
+
let cleaned = response;
|
|
43
|
+
// Remove <thinking>...</thinking> blocks
|
|
44
|
+
cleaned = cleaned.replace(/<thinking>[\s\S]*?<\/thinking>/g, '');
|
|
45
|
+
// Remove <task_analysis>...</task_analysis> blocks (keep content inside)
|
|
46
|
+
cleaned = cleaned.replace(/<\/?task_analysis>/g, '');
|
|
47
|
+
// Remove <response>...</response> tags (keep content inside)
|
|
48
|
+
cleaned = cleaned.replace(/<\/?response>/g, '');
|
|
49
|
+
// Clean up multiple consecutive newlines
|
|
50
|
+
cleaned = cleaned.replace(/\n{3,}/g, '\n\n');
|
|
51
|
+
// Clean up leading/trailing whitespace
|
|
52
|
+
cleaned = cleaned.trim();
|
|
53
|
+
return cleaned;
|
|
54
|
+
};
|
|
55
|
+
// Parse questions from orchestrator response
|
|
56
|
+
const parseQuestions = (response) => {
|
|
57
|
+
const questions = [];
|
|
58
|
+
let cleanedResponse = response;
|
|
59
|
+
// Match <question header="...">...</question> blocks
|
|
60
|
+
const questionRegex = /<question\s+header="([^"]+)">\s*([\s\S]*?)\s*<options>\s*([\s\S]*?)\s*<\/options>\s*<\/question>/g;
|
|
61
|
+
let match;
|
|
62
|
+
while ((match = questionRegex.exec(response)) !== null) {
|
|
63
|
+
const header = match[1];
|
|
64
|
+
const questionText = match[2].trim();
|
|
65
|
+
const optionsBlock = match[3];
|
|
66
|
+
// Parse options
|
|
67
|
+
const optionRegex = /<option(?:\s+recommended="true")?>(.*?)<\/option>/g;
|
|
68
|
+
const options = [];
|
|
69
|
+
let optionMatch;
|
|
70
|
+
while ((optionMatch = optionRegex.exec(optionsBlock)) !== null) {
|
|
71
|
+
const isRecommended = optionMatch[0].includes('recommended="true"');
|
|
72
|
+
options.push({
|
|
73
|
+
label: optionMatch[1].trim(),
|
|
74
|
+
recommended: isRecommended,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (options.length > 0) {
|
|
78
|
+
questions.push({
|
|
79
|
+
header,
|
|
80
|
+
question: questionText,
|
|
81
|
+
options,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Remove this question block from the response
|
|
85
|
+
cleanedResponse = cleanedResponse.replace(match[0], '').trim();
|
|
86
|
+
}
|
|
87
|
+
// Clean orchestrator internal tags from the remaining response
|
|
88
|
+
cleanedResponse = cleanOrchestratorTags(cleanedResponse);
|
|
89
|
+
return { questions, cleanedResponse };
|
|
90
|
+
};
|
|
91
|
+
// Check council availability on mount
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
checkCouncilAvailable().then(setCouncilAvailable);
|
|
94
|
+
getAvailableModels().then(setAvailableModels);
|
|
95
|
+
}, []);
|
|
25
96
|
// Detect agents mentioned in response
|
|
26
97
|
const detectMentionedAgents = (content) => {
|
|
27
98
|
const agents = [];
|
|
@@ -42,37 +113,9 @@ export const App = ({ mode, version }) => {
|
|
|
42
113
|
handleCommand(input);
|
|
43
114
|
return;
|
|
44
115
|
}
|
|
45
|
-
// Handle
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
// Enter = confirm auto-accept
|
|
49
|
-
setAutoAccept(true);
|
|
50
|
-
setPendingAutoAccept(false);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
// Any other input = cancel and process as normal input
|
|
54
|
-
setPendingAutoAccept(false);
|
|
55
|
-
}
|
|
56
|
-
// Handle approval response
|
|
57
|
-
if (awaitingApproval) {
|
|
58
|
-
// Enter (empty input) = approve
|
|
59
|
-
if (input.trim() === '') {
|
|
60
|
-
setAwaitingApproval(false);
|
|
61
|
-
if (pendingPlan) {
|
|
62
|
-
await executeTask(pendingPlan);
|
|
63
|
-
setPendingPlan(null);
|
|
64
|
-
}
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
else if (input.toLowerCase() === 'n' || input.toLowerCase() === 'no') {
|
|
68
|
-
setAwaitingApproval(false);
|
|
69
|
-
setPendingPlan(null);
|
|
70
|
-
addSystemMessage('Plan cancelled. What would you like to do instead?');
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
// Any other input cancels and starts new request
|
|
74
|
-
setAwaitingApproval(false);
|
|
75
|
-
setPendingPlan(null);
|
|
116
|
+
// Handle confirmation response
|
|
117
|
+
if (confirmationState) {
|
|
118
|
+
return; // Input is disabled when confirmation is active
|
|
76
119
|
}
|
|
77
120
|
// Add user message
|
|
78
121
|
const userMessage = {
|
|
@@ -82,13 +125,64 @@ export const App = ({ mode, version }) => {
|
|
|
82
125
|
timestamp: new Date(),
|
|
83
126
|
};
|
|
84
127
|
setMessages(prev => [...prev, userMessage]);
|
|
85
|
-
|
|
128
|
+
// Use council mode if enabled
|
|
129
|
+
if (councilMode !== 'off') {
|
|
130
|
+
await executeCouncil(input);
|
|
131
|
+
}
|
|
132
|
+
else if (executionMode === 'plan') {
|
|
86
133
|
await generatePlan(input);
|
|
87
134
|
}
|
|
88
135
|
else {
|
|
89
136
|
await executeTask(input);
|
|
90
137
|
}
|
|
91
138
|
};
|
|
139
|
+
// Execute via Council
|
|
140
|
+
const executeCouncil = async (input) => {
|
|
141
|
+
setIsProcessing(true);
|
|
142
|
+
setProcessingStartTime(new Date());
|
|
143
|
+
setActiveAgent('Orchestrator');
|
|
144
|
+
setCompletedAgents([]);
|
|
145
|
+
setError(null);
|
|
146
|
+
setCouncilStage('scout');
|
|
147
|
+
try {
|
|
148
|
+
const result = await runCouncil(input, { mode: councilMode }, (update) => {
|
|
149
|
+
// Update stage indicator
|
|
150
|
+
if (update.stage.startsWith('stage1')) {
|
|
151
|
+
setCouncilStage('stage1');
|
|
152
|
+
setActiveAgent('Orchestrator');
|
|
153
|
+
}
|
|
154
|
+
else if (update.stage.startsWith('stage2')) {
|
|
155
|
+
setCouncilStage('stage2');
|
|
156
|
+
}
|
|
157
|
+
else if (update.stage.startsWith('stage3')) {
|
|
158
|
+
setCouncilStage('stage3');
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
setCompletedAgents(['Orchestrator']);
|
|
162
|
+
setActiveAgent(undefined);
|
|
163
|
+
setCouncilStage(null);
|
|
164
|
+
const councilMessage = {
|
|
165
|
+
id: (Date.now() + 1).toString(),
|
|
166
|
+
role: 'agent',
|
|
167
|
+
agentName: 'Council',
|
|
168
|
+
content: result.final_response,
|
|
169
|
+
tokens: result.total_tokens,
|
|
170
|
+
timestamp: new Date(),
|
|
171
|
+
};
|
|
172
|
+
setMessages(prev => [...prev, councilMessage]);
|
|
173
|
+
if (result.total_tokens) {
|
|
174
|
+
setTotalTokens(prev => prev + result.total_tokens.input + result.total_tokens.output);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
setError(err instanceof Error ? err.message : 'Council error');
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
setIsProcessing(false);
|
|
182
|
+
setProcessingStartTime(null);
|
|
183
|
+
setCouncilStage(null);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
92
186
|
// Generate a plan for approval
|
|
93
187
|
const generatePlan = async (input) => {
|
|
94
188
|
setIsProcessing(true);
|
|
@@ -104,18 +198,21 @@ Respond with:
|
|
|
104
198
|
1. Task Analysis (complexity, scope)
|
|
105
199
|
2. Agents Required (list which specialists are needed)
|
|
106
200
|
3. Step-by-Step Plan (numbered steps)
|
|
107
|
-
4. Questions (if any clarification needed)
|
|
201
|
+
4. Questions (if any clarification needed - use the XML question format)
|
|
108
202
|
|
|
109
203
|
Format your response clearly with headers.`;
|
|
110
204
|
const result = await runClaude(planPrompt, mode, autoAccept);
|
|
111
|
-
|
|
205
|
+
// Check for questions in response
|
|
206
|
+
const { questions, cleanedResponse } = parseQuestions(result.response);
|
|
207
|
+
const mentioned = detectMentionedAgents(cleanedResponse || result.response);
|
|
112
208
|
setCompletedAgents(['Orchestrator', ...mentioned]);
|
|
113
209
|
setActiveAgent(undefined);
|
|
210
|
+
// Show the cleaned response (without question XML)
|
|
114
211
|
const planMessage = {
|
|
115
212
|
id: (Date.now() + 1).toString(),
|
|
116
213
|
role: 'agent',
|
|
117
214
|
agentName: 'Orchestrator',
|
|
118
|
-
content: result.response,
|
|
215
|
+
content: cleanedResponse || result.response,
|
|
119
216
|
tokens: result.tokens,
|
|
120
217
|
timestamp: new Date(),
|
|
121
218
|
};
|
|
@@ -123,14 +220,58 @@ Format your response clearly with headers.`;
|
|
|
123
220
|
if (result.tokens) {
|
|
124
221
|
setTotalTokens(prev => prev + result.tokens.input + result.tokens.output);
|
|
125
222
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
223
|
+
// Stop processing BEFORE showing wizard/confirmation to prevent overlap
|
|
224
|
+
setIsProcessing(false);
|
|
225
|
+
setProcessingStartTime(null);
|
|
226
|
+
// If questions were found, show the wizard
|
|
227
|
+
if (questions.length > 0) {
|
|
228
|
+
setQuestionWizardState({
|
|
229
|
+
questions,
|
|
230
|
+
originalInput: input,
|
|
231
|
+
onComplete: async (answers) => {
|
|
232
|
+
setQuestionWizardState(null);
|
|
233
|
+
// Format answers as follow-up message
|
|
234
|
+
const answerText = answers.map(a => `- ${a.header}: ${a.answer}`).join('\n');
|
|
235
|
+
const followUp = `My answers:\n${answerText}\n\nPlease proceed with these choices.`;
|
|
236
|
+
// Add user message with answers
|
|
237
|
+
const userAnswer = {
|
|
238
|
+
id: Date.now().toString(),
|
|
239
|
+
role: 'user',
|
|
240
|
+
content: followUp,
|
|
241
|
+
timestamp: new Date(),
|
|
242
|
+
};
|
|
243
|
+
setMessages(prev => [...prev, userAnswer]);
|
|
244
|
+
// Continue with the task including answers
|
|
245
|
+
await executeTask(`${input}\n\n${followUp}`);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// No questions, show plan approval
|
|
251
|
+
setConfirmationState({
|
|
252
|
+
message: 'Do you want to execute this plan?',
|
|
253
|
+
options: [
|
|
254
|
+
{ label: 'Yes, execute plan', value: 'yes', key: 'Enter' },
|
|
255
|
+
{ label: 'No, cancel', value: 'no', key: 'Esc' },
|
|
256
|
+
],
|
|
257
|
+
onSelect: (value) => {
|
|
258
|
+
setConfirmationState(null);
|
|
259
|
+
if (value === 'yes') {
|
|
260
|
+
executeTask(input);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
addSystemMessage('Plan cancelled.');
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
onCancel: () => {
|
|
267
|
+
setConfirmationState(null);
|
|
268
|
+
addSystemMessage('Plan cancelled.');
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
|
129
272
|
}
|
|
130
273
|
catch (err) {
|
|
131
274
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
275
|
setIsProcessing(false);
|
|
135
276
|
setProcessingStartTime(null);
|
|
136
277
|
}
|
|
@@ -144,14 +285,16 @@ Format your response clearly with headers.`;
|
|
|
144
285
|
setError(null);
|
|
145
286
|
try {
|
|
146
287
|
const result = await runClaude(input, mode, autoAccept);
|
|
147
|
-
|
|
288
|
+
// Check for questions in response
|
|
289
|
+
const { questions, cleanedResponse } = parseQuestions(result.response);
|
|
290
|
+
const mentioned = detectMentionedAgents(cleanedResponse || result.response);
|
|
148
291
|
setCompletedAgents(['Orchestrator', ...mentioned]);
|
|
149
292
|
setActiveAgent(undefined);
|
|
150
293
|
const agentMessage = {
|
|
151
294
|
id: (Date.now() + 1).toString(),
|
|
152
295
|
role: 'agent',
|
|
153
296
|
agentName: 'Orchestrator',
|
|
154
|
-
content: result.response,
|
|
297
|
+
content: cleanedResponse || result.response,
|
|
155
298
|
tokens: result.tokens,
|
|
156
299
|
timestamp: new Date(),
|
|
157
300
|
};
|
|
@@ -159,11 +302,35 @@ Format your response clearly with headers.`;
|
|
|
159
302
|
if (result.tokens) {
|
|
160
303
|
setTotalTokens(prev => prev + result.tokens.input + result.tokens.output);
|
|
161
304
|
}
|
|
305
|
+
// Stop processing BEFORE showing wizard to prevent overlap
|
|
306
|
+
setIsProcessing(false);
|
|
307
|
+
setProcessingStartTime(null);
|
|
308
|
+
// If questions were found, show the wizard
|
|
309
|
+
if (questions.length > 0) {
|
|
310
|
+
setQuestionWizardState({
|
|
311
|
+
questions,
|
|
312
|
+
originalInput: input,
|
|
313
|
+
onComplete: async (answers) => {
|
|
314
|
+
setQuestionWizardState(null);
|
|
315
|
+
// Format answers as follow-up message
|
|
316
|
+
const answerText = answers.map(a => `- ${a.header}: ${a.answer}`).join('\n');
|
|
317
|
+
const followUp = `My answers:\n${answerText}\n\nPlease proceed with these choices.`;
|
|
318
|
+
// Add user message with answers
|
|
319
|
+
const userAnswer = {
|
|
320
|
+
id: Date.now().toString(),
|
|
321
|
+
role: 'user',
|
|
322
|
+
content: followUp,
|
|
323
|
+
timestamp: new Date(),
|
|
324
|
+
};
|
|
325
|
+
setMessages(prev => [...prev, userAnswer]);
|
|
326
|
+
// Continue with the task including answers
|
|
327
|
+
await executeTask(`${input}\n\n${followUp}`);
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}
|
|
162
331
|
}
|
|
163
332
|
catch (err) {
|
|
164
333
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
165
|
-
}
|
|
166
|
-
finally {
|
|
167
334
|
setIsProcessing(false);
|
|
168
335
|
setProcessingStartTime(null);
|
|
169
336
|
}
|
|
@@ -188,7 +355,14 @@ Format your response clearly with headers.`;
|
|
|
188
355
|
exit();
|
|
189
356
|
break;
|
|
190
357
|
case 'clear':
|
|
191
|
-
|
|
358
|
+
// Keep welcome message, clear everything else
|
|
359
|
+
setMessages([{
|
|
360
|
+
id: 'welcome',
|
|
361
|
+
role: 'system',
|
|
362
|
+
content: '',
|
|
363
|
+
timestamp: new Date(),
|
|
364
|
+
isWelcome: true,
|
|
365
|
+
}]);
|
|
192
366
|
setActiveAgent(undefined);
|
|
193
367
|
setCompletedAgents([]);
|
|
194
368
|
break;
|
|
@@ -197,8 +371,27 @@ Format your response clearly with headers.`;
|
|
|
197
371
|
addSystemMessage('Plan mode enabled. I will show plans for approval before executing.');
|
|
198
372
|
break;
|
|
199
373
|
case 'auto':
|
|
200
|
-
|
|
201
|
-
|
|
374
|
+
setConfirmationState({
|
|
375
|
+
message: '⚠️ Are you sure you want to enable Auto Mode? This will execute actions without further approval.',
|
|
376
|
+
options: [
|
|
377
|
+
{ label: 'Yes, enable auto mode', value: 'yes' },
|
|
378
|
+
{ label: 'No, stay in plan mode', value: 'no' },
|
|
379
|
+
],
|
|
380
|
+
onSelect: (value) => {
|
|
381
|
+
setConfirmationState(null);
|
|
382
|
+
if (value === 'yes') {
|
|
383
|
+
setExecutionMode('auto');
|
|
384
|
+
addSystemMessage('Auto mode enabled. I will execute tasks directly without approval.');
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
addSystemMessage('Kept in plan mode.');
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
onCancel: () => {
|
|
391
|
+
setConfirmationState(null);
|
|
392
|
+
addSystemMessage('Kept in plan mode.');
|
|
393
|
+
},
|
|
394
|
+
});
|
|
202
395
|
break;
|
|
203
396
|
case 'mode':
|
|
204
397
|
addSystemMessage(`Current execution mode: ${executionMode}\nUse /plan or /auto to switch.`);
|
|
@@ -213,6 +406,42 @@ Format your response clearly with headers.`;
|
|
|
213
406
|
addSystemMessage(`Agent Status:\n${status}${completed}`);
|
|
214
407
|
}
|
|
215
408
|
break;
|
|
409
|
+
case 'council':
|
|
410
|
+
if (!councilAvailable) {
|
|
411
|
+
addSystemMessage('Council backend not available. Install: pip install -e ./python');
|
|
412
|
+
}
|
|
413
|
+
else if (councilMode === 'off') {
|
|
414
|
+
// Check available models and recommend mode
|
|
415
|
+
const hasMultipleModels = Object.values(availableModels).filter(Boolean).length > 1;
|
|
416
|
+
if (hasMultipleModels) {
|
|
417
|
+
setCouncilMode('council');
|
|
418
|
+
addSystemMessage('Council mode enabled (multi-LLM). Stage 1→2→3 consensus.');
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
setCouncilMode('solo');
|
|
422
|
+
addSystemMessage('Council mode enabled (solo). Multi-Claude personas.');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
setCouncilMode('off');
|
|
427
|
+
addSystemMessage('Council mode disabled. Using standard orchestrator.');
|
|
428
|
+
}
|
|
429
|
+
break;
|
|
430
|
+
case 'solo':
|
|
431
|
+
if (!councilAvailable) {
|
|
432
|
+
addSystemMessage('Council backend not available. Install: pip install -e ./python');
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
setCouncilMode('solo');
|
|
436
|
+
addSystemMessage('Solo council mode enabled. Uses Claude CLI with personas.');
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
case 'models':
|
|
440
|
+
const modelStatus = Object.entries(availableModels)
|
|
441
|
+
.map(([k, v]) => `${v ? '✓' : '✗'} ${k}`)
|
|
442
|
+
.join('\n');
|
|
443
|
+
addSystemMessage(`Available Models:\n${modelStatus || 'Checking...'}`);
|
|
444
|
+
break;
|
|
216
445
|
case 'help':
|
|
217
446
|
const helpMessage = {
|
|
218
447
|
id: Date.now().toString(),
|
|
@@ -226,12 +455,16 @@ Format your response clearly with headers.`;
|
|
|
226
455
|
/auto - Enable auto mode (execute directly)
|
|
227
456
|
/mode - Show current execution mode
|
|
228
457
|
/agents - Show active agents
|
|
458
|
+
/council - Toggle council mode (multi-LLM consensus)
|
|
459
|
+
/solo - Enable solo council (multi-Claude personas)
|
|
460
|
+
/models - Show available LLM models
|
|
229
461
|
/exit - Exit AGENT-K
|
|
230
462
|
|
|
231
463
|
Keyboard shortcuts:
|
|
232
464
|
↑/↓ - Browse command history
|
|
233
465
|
Tab - Autocomplete commands
|
|
234
|
-
|
|
466
|
+
Shift+Tab - Toggle auto-accept edits
|
|
467
|
+
Esc Esc - Exit
|
|
235
468
|
Ctrl+U - Clear input line`,
|
|
236
469
|
timestamp: new Date(),
|
|
237
470
|
};
|
|
@@ -245,9 +478,11 @@ Ctrl+U - Clear input line`,
|
|
|
245
478
|
content: `Session Status:
|
|
246
479
|
◇ Mode: ${mode === 'dev' ? 'Development' : 'ML Research'}
|
|
247
480
|
◇ Execution: ${executionMode === 'plan' ? 'Plan (approval required)' : 'Auto (direct execution)'}
|
|
481
|
+
◇ Council: ${councilMode === 'off' ? 'Off' : councilMode === 'council' ? 'Multi-LLM' : 'Solo (Multi-Claude)'}
|
|
248
482
|
◇ Messages: ${messages.length}
|
|
249
483
|
◇ Total Tokens: ${totalTokens}
|
|
250
484
|
◇ Active Agent: ${activeAgent || 'None'}
|
|
485
|
+
◇ Council Stage: ${councilStage || 'N/A'}
|
|
251
486
|
◇ Session Time: ${formatElapsed(startTime)}`,
|
|
252
487
|
timestamp: new Date(),
|
|
253
488
|
};
|
|
@@ -259,8 +494,21 @@ Ctrl+U - Clear input line`,
|
|
|
259
494
|
};
|
|
260
495
|
// Handle keyboard shortcuts
|
|
261
496
|
useInput((input, key) => {
|
|
262
|
-
|
|
263
|
-
|
|
497
|
+
// Double-escape to exit (like Claude Code)
|
|
498
|
+
if (key.escape) {
|
|
499
|
+
const now = Date.now();
|
|
500
|
+
if (now - lastEscapeTime < 500) {
|
|
501
|
+
// Double escape - exit
|
|
502
|
+
exit();
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
// First escape - show hint and record time
|
|
506
|
+
setLastEscapeTime(now);
|
|
507
|
+
if (!questionWizardState && !confirmationState) {
|
|
508
|
+
setShowExitHint(true);
|
|
509
|
+
setTimeout(() => setShowExitHint(false), 1500);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
264
512
|
}
|
|
265
513
|
// Shift+Tab to toggle auto-accept
|
|
266
514
|
if (key.shift && key.tab) {
|
|
@@ -270,20 +518,32 @@ Ctrl+U - Clear input line`,
|
|
|
270
518
|
}
|
|
271
519
|
else {
|
|
272
520
|
// Show confirmation prompt
|
|
273
|
-
|
|
521
|
+
setConfirmationState({
|
|
522
|
+
message: 'Enable auto-accept for code edits?',
|
|
523
|
+
options: [
|
|
524
|
+
{ label: 'Yes, enable auto-accept', value: 'yes' },
|
|
525
|
+
{ label: 'No, keep asking', value: 'no' },
|
|
526
|
+
],
|
|
527
|
+
onSelect: (value) => {
|
|
528
|
+
setConfirmationState(null);
|
|
529
|
+
if (value === 'yes') {
|
|
530
|
+
setAutoAccept(true);
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
onCancel: () => setConfirmationState(null),
|
|
534
|
+
});
|
|
274
535
|
}
|
|
275
536
|
}
|
|
276
537
|
});
|
|
277
|
-
|
|
278
|
-
const staticItems = messages.length === 0
|
|
279
|
-
? [{ id: 'welcome', isWelcome: true, role: 'system', content: '', timestamp: new Date() }]
|
|
280
|
-
: messages;
|
|
281
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: staticItems, children: (item) => {
|
|
538
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: messages, children: (item) => {
|
|
282
539
|
if ('isWelcome' in item && item.isWelcome) {
|
|
283
540
|
return _jsx(WelcomeBox, { version: version, mode: mode }, "welcome");
|
|
284
541
|
}
|
|
285
542
|
return (_jsx(ChatMessage, { role: item.role, agentName: item.agentName, content: item.content, tokens: item.tokens }, item.id));
|
|
286
|
-
} }), isProcessing && processingStartTime && (_jsx(ThinkingIndicator, { startTime: processingStartTime })), error && (_jsx(Box, { marginY: 1, marginLeft: 1, children: _jsxs(Text, { color: "#e53e3e", children: ["\u2717 Error: ", error] }) })), _jsx(
|
|
543
|
+
} }), isProcessing && processingStartTime && (_jsx(ThinkingIndicator, { startTime: processingStartTime })), error && (_jsx(Box, { marginY: 1, marginLeft: 1, children: _jsxs(Text, { color: "#e53e3e", children: ["\u2717 Error: ", error] }) })), questionWizardState ? (_jsx(QuestionWizard, { questions: questionWizardState.questions, onComplete: questionWizardState.onComplete, onCancel: () => {
|
|
544
|
+
setQuestionWizardState(null);
|
|
545
|
+
addSystemMessage('Questions cancelled.');
|
|
546
|
+
} }, `wizard-${questionWizardState.questions.map(q => q.header).join('-')}`)) : confirmationState ? (_jsx(Confirmation, { message: confirmationState.message, options: confirmationState.options, onSelect: confirmationState.onSelect, onCancel: confirmationState.onCancel })) : (_jsx(Input, { onSubmit: handleSubmit, disabled: isProcessing, placeholder: 'Try "build a password validator"' })), _jsx(StatusBar, { mode: mode, executionMode: executionMode, tokens: totalTokens, startTime: startTime, isProcessing: isProcessing, activeAgent: activeAgent, completedAgents: completedAgents, autoAccept: autoAccept, councilMode: councilMode, councilStage: councilStage, availableModels: availableModels, showExitHint: showExitHint })] }));
|
|
287
547
|
};
|
|
288
548
|
function formatElapsed(start) {
|
|
289
549
|
const secs = Math.floor((Date.now() - start.getTime()) / 1000);
|
|
@@ -10,21 +10,113 @@ const theme = {
|
|
|
10
10
|
user: '#9f7aea', // Purple for user
|
|
11
11
|
agent: '#4fd1c5', // Teal for agent
|
|
12
12
|
system: '#f6e05e', // Yellow for system
|
|
13
|
+
code: '#f6ad55', // Orange for inline code
|
|
14
|
+
header: '#63b3ed', // Blue for headers
|
|
13
15
|
};
|
|
16
|
+
// Parse inline markdown and return React elements
|
|
17
|
+
function parseInlineMarkdown(text, defaultColor) {
|
|
18
|
+
const elements = [];
|
|
19
|
+
let remaining = text;
|
|
20
|
+
let key = 0;
|
|
21
|
+
while (remaining.length > 0) {
|
|
22
|
+
// Check for bold **text**
|
|
23
|
+
const boldMatch = remaining.match(/^\*\*(.+?)\*\*/);
|
|
24
|
+
if (boldMatch) {
|
|
25
|
+
elements.push(_jsx(Text, { color: defaultColor, bold: true, children: boldMatch[1] }, key++));
|
|
26
|
+
remaining = remaining.slice(boldMatch[0].length);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Check for inline code `code`
|
|
30
|
+
const codeMatch = remaining.match(/^`([^`]+)`/);
|
|
31
|
+
if (codeMatch) {
|
|
32
|
+
elements.push(_jsx(Text, { color: theme.code, children: codeMatch[1] }, key++));
|
|
33
|
+
remaining = remaining.slice(codeMatch[0].length);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// Check for italic *text* or _text_
|
|
37
|
+
const italicMatch = remaining.match(/^(\*|_)([^*_]+)\1/);
|
|
38
|
+
if (italicMatch) {
|
|
39
|
+
elements.push(_jsx(Text, { color: defaultColor, italic: true, children: italicMatch[2] }, key++));
|
|
40
|
+
remaining = remaining.slice(italicMatch[0].length);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Find next special character
|
|
44
|
+
const nextSpecial = remaining.search(/[\*`_]/);
|
|
45
|
+
if (nextSpecial === -1) {
|
|
46
|
+
// No more special chars, add rest as plain text
|
|
47
|
+
elements.push(_jsx(Text, { color: defaultColor, children: remaining }, key++));
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
else if (nextSpecial === 0) {
|
|
51
|
+
// Special char at start but didn't match pattern - treat as literal
|
|
52
|
+
elements.push(_jsx(Text, { color: defaultColor, children: remaining[0] }, key++));
|
|
53
|
+
remaining = remaining.slice(1);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Add text before special char
|
|
57
|
+
elements.push(_jsx(Text, { color: defaultColor, children: remaining.slice(0, nextSpecial) }, key++));
|
|
58
|
+
remaining = remaining.slice(nextSpecial);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return elements;
|
|
62
|
+
}
|
|
63
|
+
// Render a single line with markdown formatting
|
|
64
|
+
function renderLine(line, index, defaultColor) {
|
|
65
|
+
// Header detection (## Header)
|
|
66
|
+
const headerMatch = line.match(/^(#{1,3})\s+(.+)$/);
|
|
67
|
+
if (headerMatch) {
|
|
68
|
+
const level = headerMatch[1].length;
|
|
69
|
+
const content = headerMatch[2];
|
|
70
|
+
return (_jsx(Box, { children: _jsx(Text, { color: theme.header, bold: true, children: content }) }, index));
|
|
71
|
+
}
|
|
72
|
+
// List item detection (- item or * item)
|
|
73
|
+
const listMatch = line.match(/^(\s*)([-*])\s+(.+)$/);
|
|
74
|
+
if (listMatch) {
|
|
75
|
+
const indent = listMatch[1];
|
|
76
|
+
const content = listMatch[3];
|
|
77
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: defaultColor, children: [indent, "\u2022 "] }), parseInlineMarkdown(content, defaultColor)] }, index));
|
|
78
|
+
}
|
|
79
|
+
// Numbered list detection (1. item)
|
|
80
|
+
const numberedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
|
|
81
|
+
if (numberedMatch) {
|
|
82
|
+
const indent = numberedMatch[1];
|
|
83
|
+
const num = numberedMatch[2];
|
|
84
|
+
const content = numberedMatch[3];
|
|
85
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: defaultColor, children: [indent, num, ". "] }), parseInlineMarkdown(content, defaultColor)] }, index));
|
|
86
|
+
}
|
|
87
|
+
// Table row detection (| col | col |)
|
|
88
|
+
if (line.includes('|') && (line.trim().startsWith('|') || line.includes(' | '))) {
|
|
89
|
+
// Keep table formatting as-is but with subtle styling
|
|
90
|
+
const isSeparator = line.match(/^[\s|:-]+$/);
|
|
91
|
+
if (isSeparator) {
|
|
92
|
+
return (_jsx(Box, { children: _jsx(Text, { color: theme.dim, children: line }) }, index));
|
|
93
|
+
}
|
|
94
|
+
return (_jsx(Box, { children: _jsx(Text, { color: defaultColor, children: line }) }, index));
|
|
95
|
+
}
|
|
96
|
+
// Regular line with inline markdown
|
|
97
|
+
return (_jsx(Box, { children: parseInlineMarkdown(line, defaultColor) }, index));
|
|
98
|
+
}
|
|
14
99
|
export const ChatMessage = ({ role, agentName = 'Agent', content, tokens, }) => {
|
|
15
100
|
const isUser = role === 'user';
|
|
16
101
|
const isSystem = role === 'system';
|
|
17
102
|
const symbolColor = isUser ? theme.user : isSystem ? theme.system : theme.agent;
|
|
18
103
|
const title = isUser ? 'You' : agentName;
|
|
104
|
+
const textColor = theme.text;
|
|
19
105
|
const termWidth = process.stdout.columns || 80;
|
|
20
106
|
const contentWidth = termWidth - 6;
|
|
107
|
+
// Split content into lines and wrap long lines
|
|
21
108
|
const lines = wrapText(content, contentWidth);
|
|
22
|
-
return (_jsxs(Box, { flexDirection: "column", marginY: 1, marginLeft: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: symbolColor, children: '◆ ' }), _jsx(Text, { color: symbolColor, bold: true, children: title })] }), _jsx(Box, { flexDirection: "column", marginLeft: 2, children: lines.map((line, i) => (
|
|
109
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, marginLeft: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: symbolColor, children: '◆ ' }), _jsx(Text, { color: symbolColor, bold: true, children: title })] }), _jsx(Box, { flexDirection: "column", marginLeft: 2, children: lines.map((line, i) => renderLine(line, i, textColor)) }), tokens && tokens.input + tokens.output > 0 && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.dim, children: ["\u2192 ", tokens.input + tokens.output, " tokens"] }) }))] }));
|
|
23
110
|
};
|
|
24
111
|
function wrapText(text, width) {
|
|
25
112
|
const lines = [];
|
|
26
113
|
const paragraphs = text.split('\n');
|
|
27
114
|
for (const para of paragraphs) {
|
|
115
|
+
// Don't wrap table lines or lines with special formatting
|
|
116
|
+
if (para.includes('|') || para.match(/^#{1,3}\s/) || para.match(/^[\s]*[-*]\s/) || para.match(/^[\s]*\d+\.\s/)) {
|
|
117
|
+
lines.push(para);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
28
120
|
if (para.length <= width) {
|
|
29
121
|
lines.push(para);
|
|
30
122
|
}
|