agentk8 2.2.6 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.1",
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": {