agentk8 2.2.6 → 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 CHANGED
@@ -4,7 +4,7 @@ import { render } from 'ink';
4
4
  import meow from 'meow';
5
5
  import { App } from './components/App.js';
6
6
  import { checkClaudeInstalled } from './lib/claude.js';
7
- const VERSION = '2.2.6';
7
+ const VERSION = '2.3.0';
8
8
  const cli = meow(`
9
9
  Usage
10
10
  $ agentk8 [options]
@@ -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,10 +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 [pendingPlan, setPendingPlan] = useState(null);
22
- const [awaitingApproval, setAwaitingApproval] = useState(false);
23
+ const [confirmationState, setConfirmationState] = useState(null);
23
24
  const [autoAccept, setAutoAccept] = useState(false);
24
- const [pendingAutoAccept, setPendingAutoAccept] = 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
+ }, []);
25
34
  // Detect agents mentioned in response
26
35
  const detectMentionedAgents = (content) => {
27
36
  const agents = [];
@@ -42,37 +51,9 @@ export const App = ({ mode, version }) => {
42
51
  handleCommand(input);
43
52
  return;
44
53
  }
45
- // Handle auto-accept confirmation
46
- if (pendingAutoAccept) {
47
- if (input.trim() === '') {
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);
54
+ // Handle confirmation response
55
+ if (confirmationState) {
56
+ return; // Input is disabled when confirmation is active
76
57
  }
77
58
  // Add user message
78
59
  const userMessage = {
@@ -82,13 +63,64 @@ export const App = ({ mode, version }) => {
82
63
  timestamp: new Date(),
83
64
  };
84
65
  setMessages(prev => [...prev, userMessage]);
85
- if (executionMode === 'plan') {
66
+ // Use council mode if enabled
67
+ if (councilMode !== 'off') {
68
+ await executeCouncil(input);
69
+ }
70
+ else if (executionMode === 'plan') {
86
71
  await generatePlan(input);
87
72
  }
88
73
  else {
89
74
  await executeTask(input);
90
75
  }
91
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
+ };
92
124
  // Generate a plan for approval
93
125
  const generatePlan = async (input) => {
94
126
  setIsProcessing(true);
@@ -123,9 +155,26 @@ Format your response clearly with headers.`;
123
155
  if (result.tokens) {
124
156
  setTotalTokens(prev => prev + result.tokens.input + result.tokens.output);
125
157
  }
126
- setPendingPlan(input);
127
- setAwaitingApproval(true);
128
- addSystemMessage('Press Enter to execute, or type "n" to cancel');
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
+ });
129
178
  }
130
179
  catch (err) {
131
180
  setError(err instanceof Error ? err.message : 'Unknown error');
@@ -197,8 +246,27 @@ Format your response clearly with headers.`;
197
246
  addSystemMessage('Plan mode enabled. I will show plans for approval before executing.');
198
247
  break;
199
248
  case 'auto':
200
- setExecutionMode('auto');
201
- addSystemMessage('Auto mode enabled. I will execute tasks directly without approval.');
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
+ });
202
270
  break;
203
271
  case 'mode':
204
272
  addSystemMessage(`Current execution mode: ${executionMode}\nUse /plan or /auto to switch.`);
@@ -213,6 +281,42 @@ Format your response clearly with headers.`;
213
281
  addSystemMessage(`Agent Status:\n${status}${completed}`);
214
282
  }
215
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;
216
320
  case 'help':
217
321
  const helpMessage = {
218
322
  id: Date.now().toString(),
@@ -226,11 +330,15 @@ Format your response clearly with headers.`;
226
330
  /auto - Enable auto mode (execute directly)
227
331
  /mode - Show current execution mode
228
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
229
336
  /exit - Exit AGENT-K
230
337
 
231
338
  Keyboard shortcuts:
232
339
  ↑/↓ - Browse command history
233
340
  Tab - Autocomplete commands
341
+ Shift+Tab - Toggle auto-accept edits
234
342
  Ctrl+C - Exit
235
343
  Ctrl+U - Clear input line`,
236
344
  timestamp: new Date(),
@@ -245,9 +353,11 @@ Ctrl+U - Clear input line`,
245
353
  content: `Session Status:
246
354
  ◇ Mode: ${mode === 'dev' ? 'Development' : 'ML Research'}
247
355
  ◇ Execution: ${executionMode === 'plan' ? 'Plan (approval required)' : 'Auto (direct execution)'}
356
+ ◇ Council: ${councilMode === 'off' ? 'Off' : councilMode === 'council' ? 'Multi-LLM' : 'Solo (Multi-Claude)'}
248
357
  ◇ Messages: ${messages.length}
249
358
  ◇ Total Tokens: ${totalTokens}
250
359
  ◇ Active Agent: ${activeAgent || 'None'}
360
+ ◇ Council Stage: ${councilStage || 'N/A'}
251
361
  ◇ Session Time: ${formatElapsed(startTime)}`,
252
362
  timestamp: new Date(),
253
363
  };
@@ -270,7 +380,20 @@ Ctrl+U - Clear input line`,
270
380
  }
271
381
  else {
272
382
  // Show confirmation prompt
273
- setPendingAutoAccept(true);
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
+ });
274
397
  }
275
398
  }
276
399
  });
@@ -283,7 +406,7 @@ Ctrl+U - Clear input line`,
283
406
  return _jsx(WelcomeBox, { version: version, mode: mode }, "welcome");
284
407
  }
285
408
  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(Input, { onSubmit: handleSubmit, disabled: isProcessing, placeholder: pendingAutoAccept ? "Auto-accept: edits applied without asking. Enter to confirm" : awaitingApproval ? "Press Enter to execute, n to cancel" : 'Try "build a password validator"' }), _jsx(StatusBar, { mode: mode, tokens: totalTokens, startTime: startTime, isProcessing: isProcessing, activeAgent: activeAgent, completedAgents: completedAgents, autoAccept: autoAccept })] }));
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 })] }));
287
410
  };
288
411
  function formatElapsed(start) {
289
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
- export const StatusBar = ({ mode, tokens, startTime, isProcessing = false, activeAgent, completedAgents = [], autoAccept = false, }) => {
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
- return (_jsxs(Box, { children: [_jsx(Text, { color: theme.dim, children: " " }), _jsx(Text, { color: theme.accent, children: modeLabel }), _jsx(Text, { color: theme.border, children: " \u2502 " }), modeAgents.map((agent, i) => {
81
- const isActive = activeAgent === agent;
82
- const leftBracket = isActive ? pulseBrackets[pulseFrame] : '[';
83
- const rightBracket = isActive ? pulseBrackets[(pulseFrame + 3) % pulseBrackets.length] === '<' ? '>' : pulseBrackets[(pulseFrame + 3) % pulseBrackets.length] === '{' ? '}' : pulseBrackets[(pulseFrame + 3) % pulseBrackets.length] === '(' ? ')' : ']' : ']';
84
- 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));
85
- }), _jsx(Text, { color: theme.border, children: " \u2502 " }), _jsx(Text, { color: theme.dim, children: "? help" }), autoAccept && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.border, children: " \u2502 " }), _jsx(Text, { color: theme.active, children: "AUTO" })] })), 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 })] })), _jsx(Text, { color: theme.dim, children: ' '.repeat(3) }), _jsxs(Text, { color: theme.accent, children: ["\u2191 ", formatTokens(tokens)] }), _jsx(Text, { color: theme.dim, children: " tokens" })] }));
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: '#4a5568',
6
- accent: '#4fd1c5',
7
- highlight: '#81e6d9',
5
+ border: '#2d3748', // gray-800
6
+ accent: '#a0aec0', // gray-400
7
+ highlight: '#e2e8f0', // gray-200
8
8
  dim: '#718096',
9
- text: '#e2e8f0',
10
- title: '#f6e05e',
11
- koi1: '#f6ad55', // orange koi
12
- koi2: '#e2e8f0', // white koi
13
- water: '#4299e1', // blue water
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.45);
19
- const rightWidth = boxWidth - leftWidth - 3;
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 - 1) }), _jsx(Text, { color: theme.title, bold: true, children: titleText }), _jsx(Text, { color: theme.border, children: '─'.repeat(titlePadRight - 1) }), _jsx(Text, { color: theme.border, children: "\u256E" })] }), _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" }), _jsxs(Box, { width: leftWidth, justifyContent: "center", children: [_jsx(Text, { color: theme.water, children: "~" }), _jsxs(Text, { color: theme.koi1, children: [" ><(((", "'", "> "] })] }), _jsx(Text, { color: theme.border, children: "\u2502" }), _jsxs(Box, { width: rightWidth, paddingLeft: 1, children: [_jsx(Text, { color: theme.text, children: "Welcome to " }), _jsx(Text, { color: theme.accent, bold: true, children: "AGENT-K" })] }), _jsx(Text, { color: theme.border, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u2502" }), _jsx(Box, { width: leftWidth, justifyContent: "center", children: _jsx(Text, { color: theme.water, children: " ~ ~ " }) }), _jsx(Text, { color: theme.border, children: "\u2502" }), _jsx(Box, { width: rightWidth, paddingLeft: 1, children: _jsx(Text, { color: theme.dim, children: "Multi-Agent Intelligence System" }) }), _jsx(Text, { color: theme.border, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.border, children: "\u2502" }), _jsxs(Box, { width: leftWidth, justifyContent: "center", children: [_jsxs(Text, { color: theme.koi2, children: [" <", "'", ")))><"] }), _jsx(Text, { color: theme.water, children: " ~" })] }), _jsx(Text, { color: theme.border, children: "\u2502" }), _jsxs(Box, { width: rightWidth, paddingLeft: 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" })] })] }));
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.2.6",
4
- "description": "Multi-Agent Claude Code Terminal Suite",
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": {