agentk8 2.2.5 → 2.3.0
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 +173 -35
- package/dist/components/Confirmation.d.ts +13 -0
- package/dist/components/Confirmation.js +39 -0
- package/dist/components/StatusBar.d.ts +5 -0
- package/dist/components/StatusBar.js +31 -7
- package/dist/components/WelcomeBox.js +17 -11
- 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,6 +7,8 @@ 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';
|
|
10
12
|
export const App = ({ mode, version }) => {
|
|
11
13
|
const { exit } = useApp();
|
|
12
14
|
const [messages, setMessages] = useState([]);
|
|
@@ -18,9 +20,17 @@ export const App = ({ mode, version }) => {
|
|
|
18
20
|
const [executionMode, setExecutionMode] = useState('plan');
|
|
19
21
|
const [activeAgent, setActiveAgent] = useState(undefined);
|
|
20
22
|
const [completedAgents, setCompletedAgents] = useState([]);
|
|
21
|
-
const [
|
|
22
|
-
const [awaitingApproval, setAwaitingApproval] = useState(false);
|
|
23
|
+
const [confirmationState, setConfirmationState] = useState(null);
|
|
23
24
|
const [autoAccept, setAutoAccept] = useState(false);
|
|
25
|
+
const [councilMode, setCouncilMode] = useState('off');
|
|
26
|
+
const [councilAvailable, setCouncilAvailable] = useState(false);
|
|
27
|
+
const [availableModels, setAvailableModels] = useState({});
|
|
28
|
+
const [councilStage, setCouncilStage] = useState(null);
|
|
29
|
+
// Check council availability on mount
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
checkCouncilAvailable().then(setCouncilAvailable);
|
|
32
|
+
getAvailableModels().then(setAvailableModels);
|
|
33
|
+
}, []);
|
|
24
34
|
// Detect agents mentioned in response
|
|
25
35
|
const detectMentionedAgents = (content) => {
|
|
26
36
|
const agents = [];
|
|
@@ -41,26 +51,9 @@ export const App = ({ mode, version }) => {
|
|
|
41
51
|
handleCommand(input);
|
|
42
52
|
return;
|
|
43
53
|
}
|
|
44
|
-
// Handle
|
|
45
|
-
if (
|
|
46
|
-
//
|
|
47
|
-
if (input.trim() === '') {
|
|
48
|
-
setAwaitingApproval(false);
|
|
49
|
-
if (pendingPlan) {
|
|
50
|
-
await executeTask(pendingPlan);
|
|
51
|
-
setPendingPlan(null);
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
else if (input.toLowerCase() === 'n' || input.toLowerCase() === 'no') {
|
|
56
|
-
setAwaitingApproval(false);
|
|
57
|
-
setPendingPlan(null);
|
|
58
|
-
addSystemMessage('Plan cancelled. What would you like to do instead?');
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
// Any other input cancels and starts new request
|
|
62
|
-
setAwaitingApproval(false);
|
|
63
|
-
setPendingPlan(null);
|
|
54
|
+
// Handle confirmation response
|
|
55
|
+
if (confirmationState) {
|
|
56
|
+
return; // Input is disabled when confirmation is active
|
|
64
57
|
}
|
|
65
58
|
// Add user message
|
|
66
59
|
const userMessage = {
|
|
@@ -70,13 +63,64 @@ export const App = ({ mode, version }) => {
|
|
|
70
63
|
timestamp: new Date(),
|
|
71
64
|
};
|
|
72
65
|
setMessages(prev => [...prev, userMessage]);
|
|
73
|
-
|
|
66
|
+
// Use council mode if enabled
|
|
67
|
+
if (councilMode !== 'off') {
|
|
68
|
+
await executeCouncil(input);
|
|
69
|
+
}
|
|
70
|
+
else if (executionMode === 'plan') {
|
|
74
71
|
await generatePlan(input);
|
|
75
72
|
}
|
|
76
73
|
else {
|
|
77
74
|
await executeTask(input);
|
|
78
75
|
}
|
|
79
76
|
};
|
|
77
|
+
// Execute via Council
|
|
78
|
+
const executeCouncil = async (input) => {
|
|
79
|
+
setIsProcessing(true);
|
|
80
|
+
setProcessingStartTime(new Date());
|
|
81
|
+
setActiveAgent('Orchestrator');
|
|
82
|
+
setCompletedAgents([]);
|
|
83
|
+
setError(null);
|
|
84
|
+
setCouncilStage('scout');
|
|
85
|
+
try {
|
|
86
|
+
const result = await runCouncil(input, { mode: councilMode }, (update) => {
|
|
87
|
+
// Update stage indicator
|
|
88
|
+
if (update.stage.startsWith('stage1')) {
|
|
89
|
+
setCouncilStage('stage1');
|
|
90
|
+
setActiveAgent('Orchestrator');
|
|
91
|
+
}
|
|
92
|
+
else if (update.stage.startsWith('stage2')) {
|
|
93
|
+
setCouncilStage('stage2');
|
|
94
|
+
}
|
|
95
|
+
else if (update.stage.startsWith('stage3')) {
|
|
96
|
+
setCouncilStage('stage3');
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
setCompletedAgents(['Orchestrator']);
|
|
100
|
+
setActiveAgent(undefined);
|
|
101
|
+
setCouncilStage(null);
|
|
102
|
+
const councilMessage = {
|
|
103
|
+
id: (Date.now() + 1).toString(),
|
|
104
|
+
role: 'agent',
|
|
105
|
+
agentName: 'Council',
|
|
106
|
+
content: result.final_response,
|
|
107
|
+
tokens: result.total_tokens,
|
|
108
|
+
timestamp: new Date(),
|
|
109
|
+
};
|
|
110
|
+
setMessages(prev => [...prev, councilMessage]);
|
|
111
|
+
if (result.total_tokens) {
|
|
112
|
+
setTotalTokens(prev => prev + result.total_tokens.input + result.total_tokens.output);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
setError(err instanceof Error ? err.message : 'Council error');
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
setIsProcessing(false);
|
|
120
|
+
setProcessingStartTime(null);
|
|
121
|
+
setCouncilStage(null);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
80
124
|
// Generate a plan for approval
|
|
81
125
|
const generatePlan = async (input) => {
|
|
82
126
|
setIsProcessing(true);
|
|
@@ -111,9 +155,26 @@ Format your response clearly with headers.`;
|
|
|
111
155
|
if (result.tokens) {
|
|
112
156
|
setTotalTokens(prev => prev + result.tokens.input + result.tokens.output);
|
|
113
157
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
158
|
+
setConfirmationState({
|
|
159
|
+
message: 'Do you want to execute this plan?',
|
|
160
|
+
options: [
|
|
161
|
+
{ label: 'Yes, execute plan', value: 'yes', key: 'Enter' },
|
|
162
|
+
{ label: 'No, cancel', value: 'no', key: 'Esc' },
|
|
163
|
+
],
|
|
164
|
+
onSelect: (value) => {
|
|
165
|
+
setConfirmationState(null);
|
|
166
|
+
if (value === 'yes') {
|
|
167
|
+
executeTask(input);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
addSystemMessage('Plan cancelled.');
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
onCancel: () => {
|
|
174
|
+
setConfirmationState(null);
|
|
175
|
+
addSystemMessage('Plan cancelled.');
|
|
176
|
+
},
|
|
177
|
+
});
|
|
117
178
|
}
|
|
118
179
|
catch (err) {
|
|
119
180
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
@@ -185,8 +246,27 @@ Format your response clearly with headers.`;
|
|
|
185
246
|
addSystemMessage('Plan mode enabled. I will show plans for approval before executing.');
|
|
186
247
|
break;
|
|
187
248
|
case 'auto':
|
|
188
|
-
|
|
189
|
-
|
|
249
|
+
setConfirmationState({
|
|
250
|
+
message: '⚠️ Are you sure you want to enable Auto Mode? This will execute actions without further approval.',
|
|
251
|
+
options: [
|
|
252
|
+
{ label: 'Yes, enable auto mode', value: 'yes' },
|
|
253
|
+
{ label: 'No, stay in plan mode', value: 'no' },
|
|
254
|
+
],
|
|
255
|
+
onSelect: (value) => {
|
|
256
|
+
setConfirmationState(null);
|
|
257
|
+
if (value === 'yes') {
|
|
258
|
+
setExecutionMode('auto');
|
|
259
|
+
addSystemMessage('Auto mode enabled. I will execute tasks directly without approval.');
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
addSystemMessage('Kept in plan mode.');
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
onCancel: () => {
|
|
266
|
+
setConfirmationState(null);
|
|
267
|
+
addSystemMessage('Kept in plan mode.');
|
|
268
|
+
},
|
|
269
|
+
});
|
|
190
270
|
break;
|
|
191
271
|
case 'mode':
|
|
192
272
|
addSystemMessage(`Current execution mode: ${executionMode}\nUse /plan or /auto to switch.`);
|
|
@@ -201,6 +281,42 @@ Format your response clearly with headers.`;
|
|
|
201
281
|
addSystemMessage(`Agent Status:\n${status}${completed}`);
|
|
202
282
|
}
|
|
203
283
|
break;
|
|
284
|
+
case 'council':
|
|
285
|
+
if (!councilAvailable) {
|
|
286
|
+
addSystemMessage('Council backend not available. Install: pip install -e ./python');
|
|
287
|
+
}
|
|
288
|
+
else if (councilMode === 'off') {
|
|
289
|
+
// Check available models and recommend mode
|
|
290
|
+
const hasMultipleModels = Object.values(availableModels).filter(Boolean).length > 1;
|
|
291
|
+
if (hasMultipleModels) {
|
|
292
|
+
setCouncilMode('council');
|
|
293
|
+
addSystemMessage('Council mode enabled (multi-LLM). Stage 1→2→3 consensus.');
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
setCouncilMode('solo');
|
|
297
|
+
addSystemMessage('Council mode enabled (solo). Multi-Claude personas.');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
setCouncilMode('off');
|
|
302
|
+
addSystemMessage('Council mode disabled. Using standard orchestrator.');
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
case 'solo':
|
|
306
|
+
if (!councilAvailable) {
|
|
307
|
+
addSystemMessage('Council backend not available. Install: pip install -e ./python');
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
setCouncilMode('solo');
|
|
311
|
+
addSystemMessage('Solo council mode enabled. Uses Claude CLI with personas.');
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
case 'models':
|
|
315
|
+
const modelStatus = Object.entries(availableModels)
|
|
316
|
+
.map(([k, v]) => `${v ? '✓' : '✗'} ${k}`)
|
|
317
|
+
.join('\n');
|
|
318
|
+
addSystemMessage(`Available Models:\n${modelStatus || 'Checking...'}`);
|
|
319
|
+
break;
|
|
204
320
|
case 'help':
|
|
205
321
|
const helpMessage = {
|
|
206
322
|
id: Date.now().toString(),
|
|
@@ -214,11 +330,15 @@ Format your response clearly with headers.`;
|
|
|
214
330
|
/auto - Enable auto mode (execute directly)
|
|
215
331
|
/mode - Show current execution mode
|
|
216
332
|
/agents - Show active agents
|
|
333
|
+
/council - Toggle council mode (multi-LLM consensus)
|
|
334
|
+
/solo - Enable solo council (multi-Claude personas)
|
|
335
|
+
/models - Show available LLM models
|
|
217
336
|
/exit - Exit AGENT-K
|
|
218
337
|
|
|
219
338
|
Keyboard shortcuts:
|
|
220
339
|
↑/↓ - Browse command history
|
|
221
340
|
Tab - Autocomplete commands
|
|
341
|
+
Shift+Tab - Toggle auto-accept edits
|
|
222
342
|
Ctrl+C - Exit
|
|
223
343
|
Ctrl+U - Clear input line`,
|
|
224
344
|
timestamp: new Date(),
|
|
@@ -233,9 +353,11 @@ Ctrl+U - Clear input line`,
|
|
|
233
353
|
content: `Session Status:
|
|
234
354
|
◇ Mode: ${mode === 'dev' ? 'Development' : 'ML Research'}
|
|
235
355
|
◇ Execution: ${executionMode === 'plan' ? 'Plan (approval required)' : 'Auto (direct execution)'}
|
|
356
|
+
◇ Council: ${councilMode === 'off' ? 'Off' : councilMode === 'council' ? 'Multi-LLM' : 'Solo (Multi-Claude)'}
|
|
236
357
|
◇ Messages: ${messages.length}
|
|
237
358
|
◇ Total Tokens: ${totalTokens}
|
|
238
359
|
◇ Active Agent: ${activeAgent || 'None'}
|
|
360
|
+
◇ Council Stage: ${councilStage || 'N/A'}
|
|
239
361
|
◇ Session Time: ${formatElapsed(startTime)}`,
|
|
240
362
|
timestamp: new Date(),
|
|
241
363
|
};
|
|
@@ -252,11 +374,27 @@ Ctrl+U - Clear input line`,
|
|
|
252
374
|
}
|
|
253
375
|
// Shift+Tab to toggle auto-accept
|
|
254
376
|
if (key.shift && key.tab) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
377
|
+
if (autoAccept) {
|
|
378
|
+
// Disable silently
|
|
379
|
+
setAutoAccept(false);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
// Show confirmation prompt
|
|
383
|
+
setConfirmationState({
|
|
384
|
+
message: 'Enable auto-accept for code edits?',
|
|
385
|
+
options: [
|
|
386
|
+
{ label: 'Yes, enable auto-accept', value: 'yes' },
|
|
387
|
+
{ label: 'No, keep asking', value: 'no' },
|
|
388
|
+
],
|
|
389
|
+
onSelect: (value) => {
|
|
390
|
+
setConfirmationState(null);
|
|
391
|
+
if (value === 'yes') {
|
|
392
|
+
setAutoAccept(true);
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
onCancel: () => setConfirmationState(null),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
260
398
|
}
|
|
261
399
|
});
|
|
262
400
|
// Prepare items for Static (include welcome box as first item)
|
|
@@ -268,7 +406,7 @@ Ctrl+U - Clear input line`,
|
|
|
268
406
|
return _jsx(WelcomeBox, { version: version, mode: mode }, "welcome");
|
|
269
407
|
}
|
|
270
408
|
return (_jsx(ChatMessage, { role: item.role, agentName: item.agentName, content: item.content, tokens: item.tokens }, item.id));
|
|
271
|
-
} }), isProcessing && processingStartTime && (_jsx(ThinkingIndicator, { startTime: processingStartTime })), error && (_jsx(Box, { marginY: 1, marginLeft: 1, children: _jsxs(Text, { color: "#e53e3e", children: ["\u2717 Error: ", error] }) })), _jsx(
|
|
409
|
+
} }), isProcessing && processingStartTime && (_jsx(ThinkingIndicator, { startTime: processingStartTime })), error && (_jsx(Box, { marginY: 1, marginLeft: 1, children: _jsxs(Text, { color: "#e53e3e", children: ["\u2717 Error: ", error] }) })), 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 })] }));
|
|
272
410
|
};
|
|
273
411
|
function formatElapsed(start) {
|
|
274
412
|
const secs = Math.floor((Date.now() - start.getTime()) / 1000);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ConfirmationOption {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
key?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ConfirmationProps {
|
|
8
|
+
message: string;
|
|
9
|
+
options: ConfirmationOption[];
|
|
10
|
+
onSelect: (value: string) => void;
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare const Confirmation: React.FC<ConfirmationProps>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
const theme = {
|
|
5
|
+
selected: '#4fd1c5', // teal-400
|
|
6
|
+
unselected: '#a0aec0', // gray-400
|
|
7
|
+
message: '#e2e8f0', // gray-200
|
|
8
|
+
key: '#718096', // gray-500
|
|
9
|
+
border: '#2d3748', // gray-800
|
|
10
|
+
};
|
|
11
|
+
export const Confirmation = ({ message, options, onSelect, onCancel, }) => {
|
|
12
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
13
|
+
useInput((input, key) => {
|
|
14
|
+
if (key.upArrow) {
|
|
15
|
+
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : options.length - 1));
|
|
16
|
+
}
|
|
17
|
+
else if (key.downArrow) {
|
|
18
|
+
setSelectedIndex((prev) => (prev < options.length - 1 ? prev + 1 : 0));
|
|
19
|
+
}
|
|
20
|
+
else if (key.return) {
|
|
21
|
+
onSelect(options[selectedIndex].value);
|
|
22
|
+
}
|
|
23
|
+
else if (key.escape) {
|
|
24
|
+
onCancel();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// Handle number keys (1-9)
|
|
28
|
+
const num = parseInt(input, 10);
|
|
29
|
+
if (!isNaN(num) && num > 0 && num <= options.length) {
|
|
30
|
+
onSelect(options[num - 1].value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.border, padding: 1, children: [_jsx(Text, { color: theme.message, bold: true, children: message }), _jsx(Box, { height: 1 }), options.map((option, index) => {
|
|
35
|
+
const isSelected = index === selectedIndex;
|
|
36
|
+
const prefix = isSelected ? '❯' : ' ';
|
|
37
|
+
return (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: isSelected ? theme.selected : theme.unselected, children: [prefix, " ", index + 1, ". ", option.label] }), option.key && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: theme.key, children: ["(", option.key, ")"] }) }))] }, option.value));
|
|
38
|
+
}), _jsx(Box, { height: 1 }), _jsx(Text, { color: theme.key, dimColor: true, children: "Use \u2191/\u2193 to navigate, Enter to select, Esc to cancel" })] }));
|
|
39
|
+
};
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { AgentName } from './AgentPanel.js';
|
|
3
|
+
type CouncilMode = 'solo' | 'council' | 'off';
|
|
3
4
|
interface StatusBarProps {
|
|
4
5
|
mode: 'dev' | 'ml';
|
|
6
|
+
executionMode: 'plan' | 'auto';
|
|
5
7
|
tokens: number;
|
|
6
8
|
startTime: Date;
|
|
7
9
|
isProcessing?: boolean;
|
|
8
10
|
activeAgent?: AgentName;
|
|
9
11
|
completedAgents?: AgentName[];
|
|
10
12
|
autoAccept?: boolean;
|
|
13
|
+
councilMode?: CouncilMode;
|
|
14
|
+
councilStage?: string | null;
|
|
15
|
+
availableModels?: Record<string, boolean>;
|
|
11
16
|
}
|
|
12
17
|
export declare const StatusBar: React.FC<StatusBarProps>;
|
|
13
18
|
export default StatusBar;
|
|
@@ -23,7 +23,13 @@ const agentIcons = {
|
|
|
23
23
|
'Data Engineer': '&',
|
|
24
24
|
Evaluator: '^',
|
|
25
25
|
};
|
|
26
|
-
|
|
26
|
+
// Model icons for council mode
|
|
27
|
+
const modelIcons = {
|
|
28
|
+
gpt: 'G',
|
|
29
|
+
gemini: 'M',
|
|
30
|
+
claude: 'C',
|
|
31
|
+
};
|
|
32
|
+
export const StatusBar = ({ mode, executionMode, tokens, startTime, isProcessing = false, activeAgent, completedAgents = [], autoAccept = false, councilMode = 'off', councilStage = null, availableModels = {}, }) => {
|
|
27
33
|
const [elapsed, setElapsed] = useState('');
|
|
28
34
|
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
29
35
|
const [pulseFrame, setPulseFrame] = useState(0);
|
|
@@ -77,11 +83,29 @@ export const StatusBar = ({ mode, tokens, startTime, isProcessing = false, activ
|
|
|
77
83
|
return theme.done;
|
|
78
84
|
return theme.dim;
|
|
79
85
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
// Get stage label for council mode
|
|
87
|
+
const getStageLabel = () => {
|
|
88
|
+
if (!councilStage)
|
|
89
|
+
return null;
|
|
90
|
+
const stages = {
|
|
91
|
+
'scout': 'Scout',
|
|
92
|
+
'stage1': '1/3',
|
|
93
|
+
'stage2': '2/3',
|
|
94
|
+
'stage3': '3/3',
|
|
95
|
+
};
|
|
96
|
+
return stages[councilStage] || councilStage;
|
|
97
|
+
};
|
|
98
|
+
return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.dim, children: " " }), _jsx(Text, { color: theme.accent, children: modeLabel }), _jsx(Text, { color: theme.border, children: " \u2502 " }), _jsx(Text, { color: executionMode === 'auto' ? theme.active : theme.accent, children: executionMode.toUpperCase() }), _jsx(Text, { color: theme.border, children: " \u2502 " }), councilMode !== 'off' ? (_jsxs(_Fragment, { children: [Object.entries(modelIcons).map(([model, icon], i) => {
|
|
99
|
+
const isAvailable = availableModels[model];
|
|
100
|
+
const color = isAvailable ? theme.done : theme.dim;
|
|
101
|
+
return (_jsxs(React.Fragment, { children: [_jsx(Text, { color: color, children: "[" }), _jsx(Text, { color: color, children: icon }), _jsx(Text, { color: color, children: "]" }), i < Object.keys(modelIcons).length - 1 && _jsx(Text, { color: theme.dim, children: " " })] }, model));
|
|
102
|
+
}), councilStage && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.border, children: " \u2502 " }), _jsxs(Text, { color: theme.active, children: ["Stage ", getStageLabel()] })] }))] })) : (
|
|
103
|
+
/* Agent boxes (standard mode) */
|
|
104
|
+
modeAgents.map((agent, i) => {
|
|
105
|
+
const isActive = activeAgent === agent;
|
|
106
|
+
const leftBracket = isActive ? pulseBrackets[pulseFrame] : '[';
|
|
107
|
+
const rightBracket = isActive ? pulseBrackets[(pulseFrame + 3) % pulseBrackets.length] === '<' ? '>' : pulseBrackets[(pulseFrame + 3) % pulseBrackets.length] === '{' ? '}' : pulseBrackets[(pulseFrame + 3) % pulseBrackets.length] === '(' ? ')' : ']' : ']';
|
|
108
|
+
return (_jsxs(React.Fragment, { children: [_jsx(Text, { color: getAgentColor(agent), children: leftBracket }), _jsx(Text, { color: getAgentColor(agent), children: agentIcons[agent] }), _jsx(Text, { color: getAgentColor(agent), children: rightBracket }), i < modeAgents.length - 1 && _jsx(Text, { color: theme.dim, children: " " })] }, agent));
|
|
109
|
+
})), _jsx(Text, { color: theme.border, children: " \u2502 " }), _jsx(Text, { color: theme.dim, children: "? help" }), councilMode !== 'off' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.border, children: " \u2502 " }), _jsx(Text, { color: theme.highlight, children: councilMode === 'council' ? 'COUNCIL' : 'SOLO' })] })), autoAccept && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.border, children: " \u2502 " }), _jsx(Text, { color: theme.active, children: "FAST" })] })), isProcessing && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.border, children: " \u2502 " }), _jsx(Text, { color: theme.highlight, children: icons.spinner[spinnerFrame] })] })), elapsed && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.border, children: " \u2502 " }), _jsx(Text, { color: theme.dim, children: elapsed })] }))] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.accent, children: ["\u2191 ", formatTokens(tokens)] }), _jsx(Text, { color: theme.dim, children: " tokens " })] })] }));
|
|
86
110
|
};
|
|
87
111
|
export default StatusBar;
|
|
@@ -2,21 +2,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
const theme = {
|
|
5
|
-
border: '#
|
|
6
|
-
accent: '#
|
|
7
|
-
highlight: '#
|
|
5
|
+
border: '#2d3748', // gray-800
|
|
6
|
+
accent: '#a0aec0', // gray-400
|
|
7
|
+
highlight: '#e2e8f0', // gray-200
|
|
8
8
|
dim: '#718096',
|
|
9
|
-
text: '#
|
|
10
|
-
title: '#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
text: '#cbd5e0',
|
|
10
|
+
title: '#718096', // gray-500
|
|
11
|
+
c1: '#a0aec0', // gray-400
|
|
12
|
+
c2: '#4a5568', // gray-700
|
|
13
|
+
c3: '#63b3ed', // blue-400 (eye)
|
|
14
14
|
};
|
|
15
15
|
export const WelcomeBox = ({ version, mode }) => {
|
|
16
16
|
const termWidth = Math.min(process.stdout.columns || 120, 120);
|
|
17
17
|
const boxWidth = termWidth - 4;
|
|
18
|
-
const leftWidth = Math.floor(boxWidth * 0.
|
|
19
|
-
const rightWidth = boxWidth - leftWidth -
|
|
18
|
+
const leftWidth = Math.floor(boxWidth * 0.55);
|
|
19
|
+
const rightWidth = boxWidth - leftWidth - 1;
|
|
20
20
|
const modeLabel = mode === 'dev' ? 'Software Development' : 'ML Research';
|
|
21
21
|
// Agent icons matching StatusBar
|
|
22
22
|
const agentIcons = {
|
|
@@ -35,6 +35,12 @@ export const WelcomeBox = ({ version, mode }) => {
|
|
|
35
35
|
const titleText = ` AGENT-K v${version} `;
|
|
36
36
|
const titlePadLeft = Math.floor((boxWidth - titleText.length) / 2);
|
|
37
37
|
const titlePadRight = boxWidth - titleText.length - titlePadLeft;
|
|
38
|
-
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u256D" }), _jsx(Text, { color: theme.border, children: '─'.repeat(titlePadLeft
|
|
38
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u256D" }), _jsx(Text, { color: theme.border, children: '─'.repeat(titlePadLeft) }), _jsx(Text, { color: theme.title, bold: true, children: titleText }), _jsx(Text, { color: theme.border, children: '─'.repeat(titlePadRight) }), _jsx(Text, { color: theme.border, children: "\u256E" })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.border, children: "\u2502" }), _jsx(Box, { width: leftWidth, justifyContent: "center", alignItems: "center", flexDirection: "column", children: _jsx(Text, { color: theme.c1, children: `
|
|
39
|
+
___________________ ____....-----....____
|
|
40
|
+
(________________LL_) ==============================
|
|
41
|
+
______\\ \\_______.--'. \`---..._____...---'
|
|
42
|
+
\`-------..__ \` ,/
|
|
43
|
+
\`-._ - - - |
|
|
44
|
+
\`-------'` }) }), _jsx(Text, { color: theme.border, children: "\u2502" }), _jsxs(Box, { width: rightWidth, flexDirection: "column", justifyContent: "center", paddingLeft: 2, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.text, children: "Welcome to " }), _jsx(Text, { color: theme.accent, bold: true, children: "AGENT-K" })] }), _jsx(Text, { color: theme.dim, children: "Pack Intelligence System" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.dim, children: "Mode: " }), _jsx(Text, { color: theme.highlight, children: modeLabel })] })] }), _jsx(Text, { color: theme.border, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u2502" }), _jsx(Text, { children: ' '.repeat(boxWidth) }), _jsx(Text, { color: theme.border, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u2502" }), _jsx(Box, { width: boxWidth, justifyContent: "center", children: agents.map((agent, i) => (_jsxs(React.Fragment, { children: [_jsxs(Text, { color: theme.accent, children: ["[", agentIcons[agent], "] "] }), _jsx(Text, { color: theme.text, children: agent }), i < agents.length - 1 && _jsx(Text, { color: theme.dim, children: " \u00B7 " })] }, agent))) }), _jsx(Text, { color: theme.border, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u2502" }), _jsx(Text, { children: ' '.repeat(boxWidth) }), _jsx(Text, { color: theme.border, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u2502" }), _jsx(Box, { width: boxWidth, justifyContent: "center", children: _jsx(Text, { color: theme.dim, children: "/help for commands \u00B7 /plan or /auto to set mode \u00B7 Ctrl+C to exit" }) }), _jsx(Text, { color: theme.border, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u2570" }), _jsx(Text, { color: theme.border, children: '─'.repeat(boxWidth) }), _jsx(Text, { color: theme.border, children: "\u256F" })] })] }));
|
|
39
45
|
};
|
|
40
46
|
export default WelcomeBox;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Council Integration for AGENT-K
|
|
3
|
+
*
|
|
4
|
+
* Connects TypeScript UI to Python council backend.
|
|
5
|
+
* Supports two modes:
|
|
6
|
+
* - Council: Multi-LLM via LiteLLM (GPT, Gemini, Claude)
|
|
7
|
+
* - Solo: Multi-Claude CLI instances with personas
|
|
8
|
+
*/
|
|
9
|
+
export interface StageUpdate {
|
|
10
|
+
stage: string;
|
|
11
|
+
message?: string;
|
|
12
|
+
responses?: Record<string, string>;
|
|
13
|
+
reviews?: Record<string, string>;
|
|
14
|
+
final?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CouncilResult {
|
|
17
|
+
query: string;
|
|
18
|
+
mode: 'council' | 'solo';
|
|
19
|
+
stages: Array<{
|
|
20
|
+
stage: number;
|
|
21
|
+
stage_name: string;
|
|
22
|
+
responses: Record<string, string>;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
}>;
|
|
25
|
+
final_response: string;
|
|
26
|
+
chairman: string;
|
|
27
|
+
total_tokens: {
|
|
28
|
+
input: number;
|
|
29
|
+
output: number;
|
|
30
|
+
};
|
|
31
|
+
timestamp: string;
|
|
32
|
+
}
|
|
33
|
+
export interface CouncilOptions {
|
|
34
|
+
mode?: 'council' | 'solo';
|
|
35
|
+
skipScout?: boolean;
|
|
36
|
+
projectRoot?: string;
|
|
37
|
+
timeout?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Run the council process.
|
|
41
|
+
*
|
|
42
|
+
* @param query User's query
|
|
43
|
+
* @param options Council options
|
|
44
|
+
* @param onStage Callback for stage updates
|
|
45
|
+
* @returns Promise resolving to final result
|
|
46
|
+
*/
|
|
47
|
+
export declare function runCouncil(query: string, options?: CouncilOptions, onStage?: (update: StageUpdate) => void): Promise<CouncilResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Check if the council backend is available.
|
|
50
|
+
*/
|
|
51
|
+
export declare function checkCouncilAvailable(): Promise<boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* Get available models status from the council backend.
|
|
54
|
+
*/
|
|
55
|
+
export declare function getAvailableModels(): Promise<Record<string, boolean>>;
|
|
56
|
+
declare const _default: {
|
|
57
|
+
runCouncil: typeof runCouncil;
|
|
58
|
+
checkCouncilAvailable: typeof checkCouncilAvailable;
|
|
59
|
+
getAvailableModels: typeof getAvailableModels;
|
|
60
|
+
};
|
|
61
|
+
export default _default;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Council Integration for AGENT-K
|
|
3
|
+
*
|
|
4
|
+
* Connects TypeScript UI to Python council backend.
|
|
5
|
+
* Supports two modes:
|
|
6
|
+
* - Council: Multi-LLM via LiteLLM (GPT, Gemini, Claude)
|
|
7
|
+
* - Solo: Multi-Claude CLI instances with personas
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
/**
|
|
15
|
+
* Run the council process.
|
|
16
|
+
*
|
|
17
|
+
* @param query User's query
|
|
18
|
+
* @param options Council options
|
|
19
|
+
* @param onStage Callback for stage updates
|
|
20
|
+
* @returns Promise resolving to final result
|
|
21
|
+
*/
|
|
22
|
+
export function runCouncil(query, options = {}, onStage) {
|
|
23
|
+
const { mode = 'council', skipScout = false, projectRoot, timeout = 300000, // 5 minutes
|
|
24
|
+
} = options;
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
// Build Python command
|
|
27
|
+
const pythonArgs = [
|
|
28
|
+
'-m', 'agentk',
|
|
29
|
+
'--mode', mode,
|
|
30
|
+
'--json',
|
|
31
|
+
];
|
|
32
|
+
if (skipScout) {
|
|
33
|
+
pythonArgs.push('--skip-scout');
|
|
34
|
+
}
|
|
35
|
+
if (projectRoot) {
|
|
36
|
+
pythonArgs.push('--project', projectRoot);
|
|
37
|
+
}
|
|
38
|
+
pythonArgs.push(query);
|
|
39
|
+
// Get the Python directory
|
|
40
|
+
const pythonDir = path.resolve(__dirname, '../../python');
|
|
41
|
+
const python = spawn('python3', pythonArgs, {
|
|
42
|
+
cwd: pythonDir,
|
|
43
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
44
|
+
env: {
|
|
45
|
+
...process.env,
|
|
46
|
+
PYTHONPATH: pythonDir,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
let stdout = '';
|
|
50
|
+
let stderr = '';
|
|
51
|
+
let resolved = false;
|
|
52
|
+
let buffer = '';
|
|
53
|
+
const timeoutId = setTimeout(() => {
|
|
54
|
+
if (!resolved) {
|
|
55
|
+
resolved = true;
|
|
56
|
+
python.kill();
|
|
57
|
+
reject(new Error('Council request timed out'));
|
|
58
|
+
}
|
|
59
|
+
}, timeout);
|
|
60
|
+
python.stdout?.on('data', (data) => {
|
|
61
|
+
const chunk = data.toString();
|
|
62
|
+
stdout += chunk;
|
|
63
|
+
buffer += chunk;
|
|
64
|
+
// Handle stream fragmentation
|
|
65
|
+
const lines = buffer.split('\n');
|
|
66
|
+
// Keep the last partial line in the buffer
|
|
67
|
+
buffer = lines.pop() || '';
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
if (!line.trim())
|
|
70
|
+
continue;
|
|
71
|
+
try {
|
|
72
|
+
const update = JSON.parse(line);
|
|
73
|
+
if (update.stage && onStage) {
|
|
74
|
+
onStage(update);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore incomplete or non-JSON lines during streaming
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
python.stderr?.on('data', (data) => {
|
|
83
|
+
stderr += data.toString();
|
|
84
|
+
});
|
|
85
|
+
python.on('close', (code) => {
|
|
86
|
+
clearTimeout(timeoutId);
|
|
87
|
+
if (resolved)
|
|
88
|
+
return;
|
|
89
|
+
resolved = true;
|
|
90
|
+
if (code !== 0 && !stdout) {
|
|
91
|
+
reject(new Error(stderr || `Council exited with code ${code}`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
// Find the last valid JSON object (the final result)
|
|
96
|
+
const lines = stdout.split('\n').filter(Boolean).reverse();
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
try {
|
|
99
|
+
const result = JSON.parse(line);
|
|
100
|
+
if (result.final_response || result.council) {
|
|
101
|
+
// Handle wrapped result
|
|
102
|
+
const councilResult = result.council || result;
|
|
103
|
+
resolve(councilResult);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Try next line
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Fallback: try parsing entire stdout
|
|
112
|
+
const result = JSON.parse(stdout);
|
|
113
|
+
resolve(result.council || result);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
reject(new Error(`Failed to parse council output: ${stdout.slice(0, 200)}`));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
python.on('error', (err) => {
|
|
120
|
+
clearTimeout(timeoutId);
|
|
121
|
+
if (resolved)
|
|
122
|
+
return;
|
|
123
|
+
resolved = true;
|
|
124
|
+
reject(new Error(`Failed to start council: ${err.message}`));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if the council backend is available.
|
|
130
|
+
*/
|
|
131
|
+
export async function checkCouncilAvailable() {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
const python = spawn('python3', ['-c', 'import agentk; print("ok")'], {
|
|
134
|
+
stdio: 'ignore',
|
|
135
|
+
cwd: path.resolve(__dirname, '../../python'),
|
|
136
|
+
env: {
|
|
137
|
+
...process.env,
|
|
138
|
+
PYTHONPATH: path.resolve(__dirname, '../../python'),
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
python.on('close', (code) => resolve(code === 0));
|
|
142
|
+
python.on('error', () => resolve(false));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get available models status from the council backend.
|
|
147
|
+
*/
|
|
148
|
+
export async function getAvailableModels() {
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
const pythonDir = path.resolve(__dirname, '../../python');
|
|
151
|
+
const python = spawn('python3', ['-c', `
|
|
152
|
+
import json
|
|
153
|
+
from agentk.llm import LLMClient
|
|
154
|
+
client = LLMClient()
|
|
155
|
+
print(json.dumps({k: v for k, v in client._available_models.items()}))
|
|
156
|
+
`], {
|
|
157
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
158
|
+
cwd: pythonDir,
|
|
159
|
+
env: {
|
|
160
|
+
...process.env,
|
|
161
|
+
PYTHONPATH: pythonDir,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
let stdout = '';
|
|
165
|
+
python.stdout?.on('data', (data) => {
|
|
166
|
+
stdout += data.toString();
|
|
167
|
+
});
|
|
168
|
+
python.on('close', (code) => {
|
|
169
|
+
if (code === 0 && stdout) {
|
|
170
|
+
try {
|
|
171
|
+
resolve(JSON.parse(stdout.trim()));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Fall through
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
resolve({ claude: true, gpt: false, gemini: false });
|
|
179
|
+
});
|
|
180
|
+
python.on('error', () => {
|
|
181
|
+
resolve({ claude: true, gpt: false, gemini: false });
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
export default {
|
|
186
|
+
runCouncil,
|
|
187
|
+
checkCouncilAvailable,
|
|
188
|
+
getAvailableModels,
|
|
189
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentk8",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Multi-
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Multi-LLM Council Terminal Suite - Three-stage consensus with GPT, Gemini, and Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|