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 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.5';
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,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 [pendingPlan, setPendingPlan] = useState(null);
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 approval response
45
- if (awaitingApproval) {
46
- // Enter (empty input) = approve
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
- if (executionMode === 'plan') {
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
- setPendingPlan(input);
115
- setAwaitingApproval(true);
116
- 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
+ });
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
- setExecutionMode('auto');
189
- 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
+ });
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
- setAutoAccept(prev => {
256
- const newValue = !prev;
257
- addSystemMessage(newValue ? 'Auto-accept enabled (edits will be applied automatically)' : 'Auto-accept disabled');
258
- return newValue;
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(Input, { onSubmit: handleSubmit, disabled: isProcessing, placeholder: 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 })] }));
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
- 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.5",
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": {