agentgui 1.0.715 → 1.0.716
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/CLAUDE.md +20 -7
- package/lib/acp-protocol.js +91 -0
- package/lib/acp-runner.js +136 -0
- package/lib/acp-sdk-manager.js +20 -64
- package/lib/agent-descriptors.js +47 -332
- package/lib/agent-registry-configs.js +125 -0
- package/lib/claude-runner.js +189 -1247
- package/lib/plugin-loader.js +3 -15
- package/lib/tool-manager.js +99 -621
- package/lib/tool-provisioner.js +93 -0
- package/lib/tool-spawner.js +121 -0
- package/lib/tool-version.js +196 -0
- package/lib/ws-handlers-conv.js +5 -198
- package/lib/ws-handlers-msg.js +119 -0
- package/lib/ws-handlers-oauth.js +76 -0
- package/lib/ws-handlers-queue.js +56 -0
- package/lib/ws-handlers-scripts.js +58 -0
- package/lib/ws-handlers-util.js +22 -206
- package/package.json +1 -1
- package/server.js +21 -3
package/lib/claude-runner.js
CHANGED
|
@@ -1,1247 +1,189 @@
|
|
|
1
|
-
import { spawnSync } from 'child_process';
|
|
2
|
-
import { execa } from 'execa';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (!options.env) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return { cmd:
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} catch {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (onEvent) { try { onEvent(parsed); } catch (e) {} }
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (result.timedOut) throw new Error(`${this.name} timeout after ${timeout}ms`);
|
|
195
|
-
|
|
196
|
-
if (authError) {
|
|
197
|
-
const err = new Error(`Authentication failed: ${authErrorMessage || 'Invalid credentials or unauthorized access'}`);
|
|
198
|
-
err.authError = true; err.nonRetryable = true; throw err;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (rateLimited) {
|
|
202
|
-
const err = new Error(`Rate limited - retry after ${retryAfterSec}s`);
|
|
203
|
-
err.rateLimited = true; err.retryAfterSec = retryAfterSec;
|
|
204
|
-
if (onRateLimit) { try { onRateLimit({ retryAfterSec }); } catch (e) {} }
|
|
205
|
-
throw err;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const code = result.exitCode;
|
|
209
|
-
if (code === 0 || outputs.length > 0) return { outputs, sessionId };
|
|
210
|
-
const stderrHint = stderrBuffer.trim() ? `: ${stderrBuffer.trim().slice(0, 200)}` : '';
|
|
211
|
-
const codeHint = code === 143 ? ' (SIGTERM - process was killed)' : code === 137 ? ' (SIGKILL - out of memory or force-killed)' : '';
|
|
212
|
-
throw new Error(`${this.name} exited with code ${code}${codeHint}${stderrHint}`);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async runACP(prompt, cwd, config = {}, _retryCount = 0) {
|
|
216
|
-
const maxRetries = config.maxRetries ?? 1;
|
|
217
|
-
try {
|
|
218
|
-
return await this._runACPOnce(prompt, cwd, config);
|
|
219
|
-
} catch (err) {
|
|
220
|
-
const isEmptyExit = err.isPrematureEnd || (err.message && err.message.includes('ACP exited with code'));
|
|
221
|
-
const isBinaryError = err.code === 'ENOENT' || (err.message && err.message.includes('ENOENT'));
|
|
222
|
-
if ((isEmptyExit || isBinaryError) && _retryCount < maxRetries) {
|
|
223
|
-
const delay = Math.min(1000 * Math.pow(2, _retryCount), 5000);
|
|
224
|
-
console.error(`[${this.id}] ACP attempt ${_retryCount + 1} failed: ${err.message}. Retrying in ${delay}ms...`);
|
|
225
|
-
await new Promise(r => setTimeout(r, delay));
|
|
226
|
-
return this.runACP(prompt, cwd, config, _retryCount + 1);
|
|
227
|
-
}
|
|
228
|
-
if (err.isPrematureEnd) {
|
|
229
|
-
const premErr = new Error(err.message);
|
|
230
|
-
premErr.isPrematureEnd = true;
|
|
231
|
-
premErr.exitCode = err.exitCode;
|
|
232
|
-
premErr.stderrText = err.stderrText;
|
|
233
|
-
throw premErr;
|
|
234
|
-
}
|
|
235
|
-
throw err;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async _runACPOnce(prompt, cwd, config = {}) {
|
|
240
|
-
return new Promise((resolve, reject) => {
|
|
241
|
-
const {
|
|
242
|
-
timeout = 300000,
|
|
243
|
-
onEvent = null,
|
|
244
|
-
onError = null
|
|
245
|
-
} = config;
|
|
246
|
-
|
|
247
|
-
let cmd, args;
|
|
248
|
-
if (this.requiresAdapter && this.adapterCommand) {
|
|
249
|
-
cmd = this.adapterCommand;
|
|
250
|
-
args = [...this.adapterArgs];
|
|
251
|
-
} else {
|
|
252
|
-
const resolved = resolveCommand(this.command, this.npxPackage);
|
|
253
|
-
cmd = resolved.cmd;
|
|
254
|
-
args = [...resolved.prefixArgs, ...this.buildArgs(prompt, config)];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const spawnOpts = getSpawnOptions(cwd);
|
|
258
|
-
if (Object.keys(this.spawnEnv).length > 0) {
|
|
259
|
-
spawnOpts.env = { ...spawnOpts.env, ...this.spawnEnv };
|
|
260
|
-
}
|
|
261
|
-
const proc = spawn(cmd, args, spawnOpts);
|
|
262
|
-
|
|
263
|
-
if (config.onPid) {
|
|
264
|
-
try { config.onPid(proc.pid); } catch (e) {}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (config.onProcess) {
|
|
268
|
-
try { config.onProcess(proc); } catch (e) {}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const outputs = [];
|
|
272
|
-
let timedOut = false;
|
|
273
|
-
let sessionId = null;
|
|
274
|
-
let requestId = 0;
|
|
275
|
-
let initialized = false;
|
|
276
|
-
let stderrText = '';
|
|
277
|
-
|
|
278
|
-
const timeoutHandle = setTimeout(() => {
|
|
279
|
-
timedOut = true;
|
|
280
|
-
proc.kill();
|
|
281
|
-
reject(new Error(`${this.name} ACP timeout after ${timeout}ms`));
|
|
282
|
-
}, timeout);
|
|
283
|
-
|
|
284
|
-
const handleMessage = (message) => {
|
|
285
|
-
const normalized = this.protocolHandler(message, { sessionId, initialized });
|
|
286
|
-
if (!normalized) {
|
|
287
|
-
if (message.id === 1 && message.result) {
|
|
288
|
-
initialized = true;
|
|
289
|
-
}
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
outputs.push(normalized);
|
|
294
|
-
|
|
295
|
-
if (normalized.session_id) {
|
|
296
|
-
sessionId = normalized.session_id;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (onEvent) {
|
|
300
|
-
try { onEvent(normalized); } catch (e) {
|
|
301
|
-
console.error(`[${this.id}] onEvent error: ${e.message}`);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
proc.stdout.on('error', () => {});
|
|
307
|
-
proc.stderr.on('error', () => {});
|
|
308
|
-
let buffer = '';
|
|
309
|
-
proc.stdout.on('data', (chunk) => {
|
|
310
|
-
if (timedOut) return;
|
|
311
|
-
|
|
312
|
-
buffer += chunk.toString();
|
|
313
|
-
const lines = buffer.split('\n');
|
|
314
|
-
buffer = lines.pop();
|
|
315
|
-
|
|
316
|
-
for (const line of lines) {
|
|
317
|
-
if (line.trim()) {
|
|
318
|
-
try {
|
|
319
|
-
const message = JSON.parse(line);
|
|
320
|
-
handleMessage(message);
|
|
321
|
-
} catch (e) {
|
|
322
|
-
console.error(`[${this.id}] JSON parse error:`, line.substring(0, 100));
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
proc.stderr.on('data', (chunk) => {
|
|
329
|
-
const errorText = chunk.toString();
|
|
330
|
-
stderrText += errorText;
|
|
331
|
-
console.error(`[${this.id}] stderr:`, errorText);
|
|
332
|
-
if (onError) {
|
|
333
|
-
try { onError(errorText); } catch (e) {}
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const initRequest = {
|
|
338
|
-
jsonrpc: '2.0',
|
|
339
|
-
id: ++requestId,
|
|
340
|
-
method: 'initialize',
|
|
341
|
-
params: {
|
|
342
|
-
protocolVersion: 1,
|
|
343
|
-
clientCapabilities: {
|
|
344
|
-
fs: { readTextFile: true, writeTextFile: true },
|
|
345
|
-
terminal: true
|
|
346
|
-
},
|
|
347
|
-
clientInfo: {
|
|
348
|
-
name: 'agentgui',
|
|
349
|
-
title: 'AgentGUI',
|
|
350
|
-
version: '1.0.0'
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
proc.stdin.on('error', () => {});
|
|
355
|
-
proc.stdin.write(JSON.stringify(initRequest) + '\n');
|
|
356
|
-
|
|
357
|
-
let sessionCreated = false;
|
|
358
|
-
|
|
359
|
-
const checkInitAndSend = () => {
|
|
360
|
-
if (initialized && !sessionCreated) {
|
|
361
|
-
sessionCreated = true;
|
|
362
|
-
|
|
363
|
-
const sessionParams = {
|
|
364
|
-
cwd: cwd,
|
|
365
|
-
mcpServers: []
|
|
366
|
-
};
|
|
367
|
-
if (config.model) sessionParams.model = config.model;
|
|
368
|
-
if (config.subAgent) sessionParams.agent = config.subAgent;
|
|
369
|
-
if (config.systemPrompt) sessionParams.systemPrompt = config.systemPrompt;
|
|
370
|
-
const sessionRequest = {
|
|
371
|
-
jsonrpc: '2.0',
|
|
372
|
-
id: ++requestId,
|
|
373
|
-
method: 'session/new',
|
|
374
|
-
params: sessionParams
|
|
375
|
-
};
|
|
376
|
-
proc.stdin.write(JSON.stringify(sessionRequest) + '\n');
|
|
377
|
-
} else if (!initialized) {
|
|
378
|
-
setTimeout(checkInitAndSend, 100);
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
let promptId = null;
|
|
383
|
-
let completed = false;
|
|
384
|
-
|
|
385
|
-
const originalHandler = handleMessage;
|
|
386
|
-
const enhancedHandler = (message) => {
|
|
387
|
-
if (message.id && message.result && message.result.sessionId) {
|
|
388
|
-
sessionId = message.result.sessionId;
|
|
389
|
-
|
|
390
|
-
promptId = ++requestId;
|
|
391
|
-
const promptRequest = {
|
|
392
|
-
jsonrpc: '2.0',
|
|
393
|
-
id: promptId,
|
|
394
|
-
method: 'session/prompt',
|
|
395
|
-
params: {
|
|
396
|
-
sessionId: sessionId,
|
|
397
|
-
prompt: [{ type: 'text', text: prompt }]
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
proc.stdin.write(JSON.stringify(promptRequest) + '\n');
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (message.id === promptId && message.result && message.result.stopReason) {
|
|
405
|
-
completed = true;
|
|
406
|
-
draining = true;
|
|
407
|
-
clearTimeout(timeoutHandle);
|
|
408
|
-
// Wait a short time for any remaining events to be flushed before killing
|
|
409
|
-
setTimeout(() => {
|
|
410
|
-
draining = false;
|
|
411
|
-
try { proc.kill(); } catch (e) {}
|
|
412
|
-
resolve({ outputs, sessionId });
|
|
413
|
-
}, 1000);
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (message.id === promptId && message.error) {
|
|
418
|
-
completed = true;
|
|
419
|
-
draining = true;
|
|
420
|
-
clearTimeout(timeoutHandle);
|
|
421
|
-
// Process the error message first, then delay for remaining events
|
|
422
|
-
originalHandler(message);
|
|
423
|
-
setTimeout(() => {
|
|
424
|
-
draining = false;
|
|
425
|
-
try { proc.kill(); } catch (e) {}
|
|
426
|
-
reject(new Error(message.error.message || 'ACP prompt error'));
|
|
427
|
-
}, 1000);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
originalHandler(message);
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
buffer = '';
|
|
435
|
-
proc.stdout.removeAllListeners('data');
|
|
436
|
-
let draining = false;
|
|
437
|
-
proc.stdout.on('data', (chunk) => {
|
|
438
|
-
if (timedOut) return;
|
|
439
|
-
// Continue processing during drain period after stopReason/error
|
|
440
|
-
if (completed && !draining) return;
|
|
441
|
-
|
|
442
|
-
buffer += chunk.toString();
|
|
443
|
-
const lines = buffer.split('\n');
|
|
444
|
-
buffer = lines.pop();
|
|
445
|
-
|
|
446
|
-
for (const line of lines) {
|
|
447
|
-
if (line.trim()) {
|
|
448
|
-
try {
|
|
449
|
-
const message = JSON.parse(line);
|
|
450
|
-
|
|
451
|
-
if (message.id === 1 && message.result) {
|
|
452
|
-
initialized = true;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
enhancedHandler(message);
|
|
456
|
-
} catch (e) {
|
|
457
|
-
console.error(`[${this.id}] JSON parse error:`, line.substring(0, 100));
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
setTimeout(checkInitAndSend, 200);
|
|
464
|
-
|
|
465
|
-
proc.on('close', (code) => {
|
|
466
|
-
clearTimeout(timeoutHandle);
|
|
467
|
-
if (timedOut || completed) return;
|
|
468
|
-
|
|
469
|
-
// Flush any remaining buffer content
|
|
470
|
-
if (buffer.trim()) {
|
|
471
|
-
try {
|
|
472
|
-
const message = JSON.parse(buffer.trim());
|
|
473
|
-
if (message.id === 1 && message.result) {
|
|
474
|
-
initialized = true;
|
|
475
|
-
}
|
|
476
|
-
enhancedHandler(message);
|
|
477
|
-
} catch (e) {
|
|
478
|
-
// Buffer might be incomplete, ignore parse errors on close
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (code === 0 || outputs.length > 0) {
|
|
483
|
-
resolve({ outputs, sessionId });
|
|
484
|
-
} else {
|
|
485
|
-
const detail = stderrText ? `: ${stderrText.substring(0, 200)}` : '';
|
|
486
|
-
const err = new Error(`${this.name} ACP exited with code ${code}${detail}`);
|
|
487
|
-
err.isPrematureEnd = true;
|
|
488
|
-
err.exitCode = code;
|
|
489
|
-
err.stderrText = stderrText;
|
|
490
|
-
reject(err);
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
proc.on('error', (err) => {
|
|
495
|
-
clearTimeout(timeoutHandle);
|
|
496
|
-
reject(err);
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Agent Registry
|
|
504
|
-
*/
|
|
505
|
-
class AgentRegistry {
|
|
506
|
-
constructor() {
|
|
507
|
-
this.agents = new Map();
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
register(config) {
|
|
511
|
-
const runner = new AgentRunner(config);
|
|
512
|
-
this.agents.set(config.id, runner);
|
|
513
|
-
return runner;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
get(agentId) {
|
|
517
|
-
return this.agents.get(agentId);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
has(agentId) {
|
|
521
|
-
return this.agents.has(agentId);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
list() {
|
|
525
|
-
return Array.from(this.agents.values()).map(a => ({
|
|
526
|
-
id: a.id,
|
|
527
|
-
name: a.name,
|
|
528
|
-
command: a.command,
|
|
529
|
-
protocol: a.protocol,
|
|
530
|
-
requiresAdapter: a.requiresAdapter,
|
|
531
|
-
supportedFeatures: a.supportedFeatures,
|
|
532
|
-
npxPackage: a.npxPackage
|
|
533
|
-
}));
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
listACPAvailable() {
|
|
537
|
-
return this.list().filter(agent => {
|
|
538
|
-
try {
|
|
539
|
-
const whichCmd = isWindows ? 'where' : 'which';
|
|
540
|
-
const which = spawnSync(whichCmd, [agent.command], { encoding: 'utf-8', timeout: 3000 });
|
|
541
|
-
if (which.status === 0) {
|
|
542
|
-
const binPath = (which.stdout || '').trim().split('\n')[0].trim();
|
|
543
|
-
if (binPath) {
|
|
544
|
-
const check = spawnSync(binPath, ['--version'], { encoding: 'utf-8', timeout: 10000, shell: isWindows });
|
|
545
|
-
if (check.status === 0 && (check.stdout || '').trim().length > 0) return true;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
const a = this.agents.get(agent.id);
|
|
549
|
-
if (a && a.npxPackage) {
|
|
550
|
-
const npxCheck = spawnSync(whichCmd, ['npx'], { encoding: 'utf-8', timeout: 3000 });
|
|
551
|
-
if (npxCheck.status === 0) return true;
|
|
552
|
-
const bunCheck = spawnSync(whichCmd, ['bun'], { encoding: 'utf-8', timeout: 3000 });
|
|
553
|
-
if (bunCheck.status === 0) return true;
|
|
554
|
-
}
|
|
555
|
-
return false;
|
|
556
|
-
} catch {
|
|
557
|
-
return false;
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Create global registry
|
|
564
|
-
const registry = new AgentRegistry();
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Claude Code Agent
|
|
568
|
-
* Uses direct JSON streaming protocol
|
|
569
|
-
*/
|
|
570
|
-
registry.register({
|
|
571
|
-
id: 'claude-code',
|
|
572
|
-
name: 'Claude Code',
|
|
573
|
-
command: 'claude',
|
|
574
|
-
protocol: 'direct',
|
|
575
|
-
supportsStdin: false, // stdin must be closed — claude blocks when stdin is an open socket (non-TTY)
|
|
576
|
-
closeStdin: true, // close stdin on spawn so claude uses positional arg prompt immediately
|
|
577
|
-
useJsonRpcStdin: false,
|
|
578
|
-
supportedFeatures: ['streaming', 'resume', 'system-prompt', 'permissions-skip', 'steering'],
|
|
579
|
-
spawnEnv: { MAX_THINKING_TOKENS: '0', AGENTGUI_SUBPROCESS: '1' },
|
|
580
|
-
|
|
581
|
-
buildArgs(prompt, config) {
|
|
582
|
-
const {
|
|
583
|
-
verbose = true,
|
|
584
|
-
outputFormat = 'stream-json',
|
|
585
|
-
print = true,
|
|
586
|
-
resumeSessionId = null,
|
|
587
|
-
systemPrompt = null,
|
|
588
|
-
model = null
|
|
589
|
-
} = config;
|
|
590
|
-
|
|
591
|
-
const flags = [];
|
|
592
|
-
if (print) flags.push('--print');
|
|
593
|
-
if (verbose) flags.push('--verbose');
|
|
594
|
-
flags.push(`--output-format=${outputFormat}`);
|
|
595
|
-
if (model) flags.push('--model', model);
|
|
596
|
-
if (resumeSessionId) flags.push('--resume', resumeSessionId);
|
|
597
|
-
if (systemPrompt) flags.push('--append-system-prompt', systemPrompt);
|
|
598
|
-
flags.push('--dangerously-skip-permissions');
|
|
599
|
-
flags.push(typeof prompt === 'string' ? prompt : String(prompt)); // positional arg - stdin stays open separately for steering
|
|
600
|
-
|
|
601
|
-
return flags;
|
|
602
|
-
},
|
|
603
|
-
|
|
604
|
-
parseOutput(line) {
|
|
605
|
-
try {
|
|
606
|
-
const entry = JSON.parse(line);
|
|
607
|
-
if (!entry || typeof entry !== 'object') return null;
|
|
608
|
-
|
|
609
|
-
// Filter isMeta user entries (local command caveats, not real conversation turns)
|
|
610
|
-
if (entry.type === 'user' && entry.isMeta === true) return null;
|
|
611
|
-
|
|
612
|
-
// Mark isCompactSummary entries so renderer can display them specially
|
|
613
|
-
// (already passes through as-is, renderer checks this flag)
|
|
614
|
-
|
|
615
|
-
// Detect rate limit via isApiErrorMessage + error field
|
|
616
|
-
if (entry.isApiErrorMessage === true && entry.error === 'rate_limit') {
|
|
617
|
-
entry._rateLimitDetected = true;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Annotate streaming fragments vs final consolidated response
|
|
621
|
-
// assistant entries with stop_reason: null are fragments; non-null stop_reason is final
|
|
622
|
-
if (entry.type === 'assistant' && entry.message) {
|
|
623
|
-
entry._isFragment = entry.message.stop_reason === null || entry.message.stop_reason === undefined;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Extract turn duration from system/turn_duration entries
|
|
627
|
-
if (entry.type === 'system' && entry.subtype === 'turn_duration' && entry.durationMs) {
|
|
628
|
-
entry._turnDurationMs = entry.durationMs;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Extract compact boundary metadata
|
|
632
|
-
if (entry.type === 'system' && entry.subtype === 'compact_boundary' && entry.compactMetadata) {
|
|
633
|
-
entry._preTokens = entry.compactMetadata.preTokens;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Normalize cache usage fields into a flat structure for cost tracking
|
|
637
|
-
if (entry.message?.usage) {
|
|
638
|
-
const u = entry.message.usage;
|
|
639
|
-
entry._cacheUsage = {
|
|
640
|
-
cache_creation: u.cache_creation_input_tokens || u['cache_creation.ephemeral_1h_input_tokens'] || u['cache_creation.ephemeral_5m_input_tokens'] || 0,
|
|
641
|
-
cache_read: u.cache_read_input_tokens || 0
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return entry;
|
|
646
|
-
} catch {
|
|
647
|
-
return null;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* OpenCode Agent
|
|
654
|
-
* Native ACP support
|
|
655
|
-
*/
|
|
656
|
-
registry.register({
|
|
657
|
-
id: 'opencode',
|
|
658
|
-
name: 'OpenCode',
|
|
659
|
-
command: 'opencode',
|
|
660
|
-
protocol: 'acp',
|
|
661
|
-
supportsStdin: false,
|
|
662
|
-
npxPackage: 'opencode-ai',
|
|
663
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
664
|
-
|
|
665
|
-
buildArgs(prompt, config) {
|
|
666
|
-
return ['acp'];
|
|
667
|
-
},
|
|
668
|
-
|
|
669
|
-
protocolHandler(message, context) {
|
|
670
|
-
if (!message || typeof message !== 'object') return null;
|
|
671
|
-
|
|
672
|
-
// Handle ACP session/update notifications
|
|
673
|
-
if (message.method === 'session/update') {
|
|
674
|
-
const params = message.params || {};
|
|
675
|
-
const update = params.update || {};
|
|
676
|
-
|
|
677
|
-
// Agent message chunk (text response)
|
|
678
|
-
if (update.sessionUpdate === 'agent_message_chunk' && update.content) {
|
|
679
|
-
let contentBlock;
|
|
680
|
-
|
|
681
|
-
// Handle different content formats
|
|
682
|
-
if (typeof update.content === 'string') {
|
|
683
|
-
contentBlock = { type: 'text', text: update.content };
|
|
684
|
-
} else if (update.content.type === 'text' && update.content.text) {
|
|
685
|
-
contentBlock = update.content;
|
|
686
|
-
} else if (update.content.text) {
|
|
687
|
-
contentBlock = { type: 'text', text: update.content.text };
|
|
688
|
-
} else if (update.content.content) {
|
|
689
|
-
const inner = update.content.content;
|
|
690
|
-
if (typeof inner === 'string') {
|
|
691
|
-
contentBlock = { type: 'text', text: inner };
|
|
692
|
-
} else if (inner.type === 'text' && inner.text) {
|
|
693
|
-
contentBlock = inner;
|
|
694
|
-
} else {
|
|
695
|
-
contentBlock = { type: 'text', text: JSON.stringify(inner) };
|
|
696
|
-
}
|
|
697
|
-
} else {
|
|
698
|
-
contentBlock = { type: 'text', text: JSON.stringify(update.content) };
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
return {
|
|
702
|
-
type: 'assistant',
|
|
703
|
-
message: {
|
|
704
|
-
role: 'assistant',
|
|
705
|
-
content: [contentBlock]
|
|
706
|
-
},
|
|
707
|
-
session_id: params.sessionId
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// Tool call
|
|
712
|
-
if (update.sessionUpdate === 'tool_call') {
|
|
713
|
-
return {
|
|
714
|
-
type: 'assistant',
|
|
715
|
-
message: {
|
|
716
|
-
role: 'assistant',
|
|
717
|
-
content: [{
|
|
718
|
-
type: 'tool_use',
|
|
719
|
-
id: update.toolCallId,
|
|
720
|
-
name: update.title || update.kind || 'tool',
|
|
721
|
-
kind: update.kind || 'other',
|
|
722
|
-
input: update.rawInput || update.input || {}
|
|
723
|
-
}]
|
|
724
|
-
},
|
|
725
|
-
session_id: params.sessionId
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// Tool call update (result) - handle all statuses
|
|
730
|
-
if (update.sessionUpdate === 'tool_call_update') {
|
|
731
|
-
const status = update.status;
|
|
732
|
-
const isError = status === 'failed';
|
|
733
|
-
const isCompleted = status === 'completed';
|
|
734
|
-
|
|
735
|
-
if (!isCompleted && !isError) {
|
|
736
|
-
return {
|
|
737
|
-
type: 'tool_status',
|
|
738
|
-
tool_use_id: update.toolCallId,
|
|
739
|
-
status: status,
|
|
740
|
-
kind: update.kind || 'other',
|
|
741
|
-
locations: update.locations || [],
|
|
742
|
-
session_id: params.sessionId
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
const contentParts = [];
|
|
747
|
-
if (update.content && Array.isArray(update.content)) {
|
|
748
|
-
for (const item of update.content) {
|
|
749
|
-
if (item.type === 'content' && item.content) {
|
|
750
|
-
const innerContent = item.content;
|
|
751
|
-
if (innerContent.type === 'text' && innerContent.text) {
|
|
752
|
-
contentParts.push(innerContent.text);
|
|
753
|
-
} else if (innerContent.type === 'resource' && innerContent.resource) {
|
|
754
|
-
contentParts.push(innerContent.resource.text || JSON.stringify(innerContent.resource));
|
|
755
|
-
} else {
|
|
756
|
-
contentParts.push(JSON.stringify(innerContent));
|
|
757
|
-
}
|
|
758
|
-
} else if (item.type === 'diff') {
|
|
759
|
-
const diffText = item.oldText
|
|
760
|
-
? `--- ${item.path}\n+++ ${item.path}\n${item.oldText}\n---\n${item.newText}`
|
|
761
|
-
: `+++ ${item.path}\n${item.newText}`;
|
|
762
|
-
contentParts.push(diffText);
|
|
763
|
-
} else if (item.type === 'terminal') {
|
|
764
|
-
contentParts.push(`[Terminal: ${item.terminalId}]`);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const combinedContent = contentParts.join('\n') || (update.rawOutput ? JSON.stringify(update.rawOutput) : '');
|
|
770
|
-
|
|
771
|
-
return {
|
|
772
|
-
type: 'user',
|
|
773
|
-
message: {
|
|
774
|
-
role: 'user',
|
|
775
|
-
content: [{
|
|
776
|
-
type: 'tool_result',
|
|
777
|
-
tool_use_id: update.toolCallId,
|
|
778
|
-
content: combinedContent,
|
|
779
|
-
is_error: isError
|
|
780
|
-
}]
|
|
781
|
-
},
|
|
782
|
-
session_id: params.sessionId
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// Usage update
|
|
787
|
-
if (update.sessionUpdate === 'usage_update') {
|
|
788
|
-
return {
|
|
789
|
-
type: 'usage',
|
|
790
|
-
usage: {
|
|
791
|
-
used: update.used,
|
|
792
|
-
size: update.size,
|
|
793
|
-
cost: update.cost
|
|
794
|
-
},
|
|
795
|
-
session_id: params.sessionId
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Plan update
|
|
800
|
-
if (update.sessionUpdate === 'plan') {
|
|
801
|
-
return {
|
|
802
|
-
type: 'plan',
|
|
803
|
-
entries: update.entries || [],
|
|
804
|
-
session_id: params.sessionId
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// Skip other updates like available_commands_update
|
|
809
|
-
return null;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Handle prompt response (end of turn)
|
|
813
|
-
if (message.id && message.result && message.result.stopReason) {
|
|
814
|
-
return {
|
|
815
|
-
type: 'result',
|
|
816
|
-
result: '',
|
|
817
|
-
stopReason: message.result.stopReason,
|
|
818
|
-
usage: message.result.usage,
|
|
819
|
-
session_id: context.sessionId
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (message.method === 'error' || message.error) {
|
|
824
|
-
return {
|
|
825
|
-
type: 'error',
|
|
826
|
-
error: message.error || message.params || { message: 'Unknown error' }
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
return null;
|
|
831
|
-
}
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
/**
|
|
835
|
-
* Common ACP protocol handler for all ACP agents
|
|
836
|
-
*/
|
|
837
|
-
function createACPProtocolHandler() {
|
|
838
|
-
return function(message, context) {
|
|
839
|
-
if (!message || typeof message !== 'object') return null;
|
|
840
|
-
|
|
841
|
-
// Handle ACP session/update notifications
|
|
842
|
-
if (message.method === 'session/update') {
|
|
843
|
-
const params = message.params || {};
|
|
844
|
-
const update = params.update || {};
|
|
845
|
-
|
|
846
|
-
// Agent message chunk (text response)
|
|
847
|
-
if (update.sessionUpdate === 'agent_message_chunk' && update.content) {
|
|
848
|
-
let contentBlock;
|
|
849
|
-
|
|
850
|
-
// Handle different content formats
|
|
851
|
-
if (typeof update.content === 'string') {
|
|
852
|
-
contentBlock = { type: 'text', text: update.content };
|
|
853
|
-
} else if (update.content.type === 'text' && update.content.text) {
|
|
854
|
-
contentBlock = update.content;
|
|
855
|
-
} else if (update.content.text) {
|
|
856
|
-
contentBlock = { type: 'text', text: update.content.text };
|
|
857
|
-
} else if (update.content.content) {
|
|
858
|
-
const inner = update.content.content;
|
|
859
|
-
if (typeof inner === 'string') {
|
|
860
|
-
contentBlock = { type: 'text', text: inner };
|
|
861
|
-
} else if (inner.type === 'text' && inner.text) {
|
|
862
|
-
contentBlock = inner;
|
|
863
|
-
} else {
|
|
864
|
-
contentBlock = { type: 'text', text: JSON.stringify(inner) };
|
|
865
|
-
}
|
|
866
|
-
} else {
|
|
867
|
-
contentBlock = { type: 'text', text: JSON.stringify(update.content) };
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
return {
|
|
871
|
-
type: 'assistant',
|
|
872
|
-
message: {
|
|
873
|
-
role: 'assistant',
|
|
874
|
-
content: [contentBlock]
|
|
875
|
-
},
|
|
876
|
-
session_id: params.sessionId
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
// Tool call
|
|
881
|
-
if (update.sessionUpdate === 'tool_call') {
|
|
882
|
-
return {
|
|
883
|
-
type: 'assistant',
|
|
884
|
-
message: {
|
|
885
|
-
role: 'assistant',
|
|
886
|
-
content: [{
|
|
887
|
-
type: 'tool_use',
|
|
888
|
-
id: update.toolCallId,
|
|
889
|
-
name: update.title || update.kind || 'tool',
|
|
890
|
-
kind: update.kind || 'other',
|
|
891
|
-
input: update.rawInput || update.input || {}
|
|
892
|
-
}]
|
|
893
|
-
},
|
|
894
|
-
session_id: params.sessionId
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// Tool call update (result) - handle all statuses
|
|
899
|
-
if (update.sessionUpdate === 'tool_call_update') {
|
|
900
|
-
const status = update.status;
|
|
901
|
-
const isError = status === 'failed';
|
|
902
|
-
const isCompleted = status === 'completed';
|
|
903
|
-
|
|
904
|
-
if (!isCompleted && !isError) {
|
|
905
|
-
return {
|
|
906
|
-
type: 'tool_status',
|
|
907
|
-
tool_use_id: update.toolCallId,
|
|
908
|
-
status: status,
|
|
909
|
-
kind: update.kind || 'other',
|
|
910
|
-
locations: update.locations || [],
|
|
911
|
-
session_id: params.sessionId
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
const contentParts = [];
|
|
916
|
-
if (update.content && Array.isArray(update.content)) {
|
|
917
|
-
for (const item of update.content) {
|
|
918
|
-
if (item.type === 'content' && item.content) {
|
|
919
|
-
const innerContent = item.content;
|
|
920
|
-
if (innerContent.type === 'text' && innerContent.text) {
|
|
921
|
-
contentParts.push(innerContent.text);
|
|
922
|
-
} else if (innerContent.type === 'resource' && innerContent.resource) {
|
|
923
|
-
contentParts.push(innerContent.resource.text || JSON.stringify(innerContent.resource));
|
|
924
|
-
} else {
|
|
925
|
-
contentParts.push(JSON.stringify(innerContent));
|
|
926
|
-
}
|
|
927
|
-
} else if (item.type === 'diff') {
|
|
928
|
-
const diffText = item.oldText
|
|
929
|
-
? `--- ${item.path}\n+++ ${item.path}\n${item.oldText}\n---\n${item.newText}`
|
|
930
|
-
: `+++ ${item.path}\n${item.newText}`;
|
|
931
|
-
contentParts.push(diffText);
|
|
932
|
-
} else if (item.type === 'terminal') {
|
|
933
|
-
contentParts.push(`[Terminal: ${item.terminalId}]`);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
const combinedContent = contentParts.join('\n') || (update.rawOutput ? JSON.stringify(update.rawOutput) : '');
|
|
939
|
-
|
|
940
|
-
return {
|
|
941
|
-
type: 'user',
|
|
942
|
-
message: {
|
|
943
|
-
role: 'user',
|
|
944
|
-
content: [{
|
|
945
|
-
type: 'tool_result',
|
|
946
|
-
tool_use_id: update.toolCallId,
|
|
947
|
-
content: combinedContent,
|
|
948
|
-
is_error: isError
|
|
949
|
-
}]
|
|
950
|
-
},
|
|
951
|
-
session_id: params.sessionId
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// Usage update
|
|
956
|
-
if (update.sessionUpdate === 'usage_update') {
|
|
957
|
-
return {
|
|
958
|
-
type: 'usage',
|
|
959
|
-
usage: {
|
|
960
|
-
used: update.used,
|
|
961
|
-
size: update.size,
|
|
962
|
-
cost: update.cost
|
|
963
|
-
},
|
|
964
|
-
session_id: params.sessionId
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// Plan update
|
|
969
|
-
if (update.sessionUpdate === 'plan') {
|
|
970
|
-
return {
|
|
971
|
-
type: 'plan',
|
|
972
|
-
entries: update.entries || [],
|
|
973
|
-
session_id: params.sessionId
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
return null;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
// Handle prompt response (end of turn)
|
|
981
|
-
if (message.id && message.result && message.result.stopReason) {
|
|
982
|
-
return {
|
|
983
|
-
type: 'result',
|
|
984
|
-
result: '',
|
|
985
|
-
stopReason: message.result.stopReason,
|
|
986
|
-
usage: message.result.usage,
|
|
987
|
-
session_id: context.sessionId
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
if (message.method === 'error' || message.error) {
|
|
992
|
-
return {
|
|
993
|
-
type: 'error',
|
|
994
|
-
error: message.error || message.params || { message: 'Unknown error' }
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
return null;
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// Shared ACP handler
|
|
1003
|
-
const acpProtocolHandler = createACPProtocolHandler();
|
|
1004
|
-
|
|
1005
|
-
/**
|
|
1006
|
-
* Gemini CLI Agent
|
|
1007
|
-
* Native ACP support
|
|
1008
|
-
*/
|
|
1009
|
-
registry.register({
|
|
1010
|
-
id: 'gemini',
|
|
1011
|
-
name: 'Gemini CLI',
|
|
1012
|
-
command: 'gemini',
|
|
1013
|
-
protocol: 'acp',
|
|
1014
|
-
supportsStdin: false,
|
|
1015
|
-
npxPackage: '@google/gemini-cli',
|
|
1016
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1017
|
-
buildArgs(prompt, config) {
|
|
1018
|
-
const args = ['--experimental-acp', '--yolo'];
|
|
1019
|
-
if (config?.model) args.push('--model', config.model);
|
|
1020
|
-
return args;
|
|
1021
|
-
},
|
|
1022
|
-
protocolHandler: acpProtocolHandler
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
/**
|
|
1026
|
-
* Goose Agent
|
|
1027
|
-
* Native ACP support
|
|
1028
|
-
*/
|
|
1029
|
-
registry.register({
|
|
1030
|
-
id: 'goose',
|
|
1031
|
-
name: 'Goose',
|
|
1032
|
-
command: 'goose',
|
|
1033
|
-
protocol: 'acp',
|
|
1034
|
-
supportsStdin: false,
|
|
1035
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1036
|
-
buildArgs: () => ['acp'],
|
|
1037
|
-
protocolHandler: acpProtocolHandler
|
|
1038
|
-
});
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* OpenHands Agent
|
|
1042
|
-
* Native ACP support
|
|
1043
|
-
*/
|
|
1044
|
-
registry.register({
|
|
1045
|
-
id: 'openhands',
|
|
1046
|
-
name: 'OpenHands',
|
|
1047
|
-
command: 'openhands',
|
|
1048
|
-
protocol: 'acp',
|
|
1049
|
-
supportsStdin: false,
|
|
1050
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1051
|
-
buildArgs: () => ['acp'],
|
|
1052
|
-
protocolHandler: acpProtocolHandler
|
|
1053
|
-
});
|
|
1054
|
-
|
|
1055
|
-
/**
|
|
1056
|
-
* Augment Code Agent - Native ACP support
|
|
1057
|
-
*/
|
|
1058
|
-
registry.register({
|
|
1059
|
-
id: 'augment',
|
|
1060
|
-
name: 'Augment Code',
|
|
1061
|
-
command: 'augment',
|
|
1062
|
-
protocol: 'acp',
|
|
1063
|
-
supportsStdin: false,
|
|
1064
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1065
|
-
buildArgs: () => ['acp'],
|
|
1066
|
-
protocolHandler: acpProtocolHandler
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
/**
|
|
1070
|
-
* Cline Agent - Native ACP support
|
|
1071
|
-
*/
|
|
1072
|
-
registry.register({
|
|
1073
|
-
id: 'cline',
|
|
1074
|
-
name: 'Cline',
|
|
1075
|
-
command: 'cline',
|
|
1076
|
-
protocol: 'acp',
|
|
1077
|
-
supportsStdin: false,
|
|
1078
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1079
|
-
buildArgs: () => ['acp'],
|
|
1080
|
-
protocolHandler: acpProtocolHandler
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
/**
|
|
1084
|
-
* Kimi CLI Agent (Moonshot AI) - Native ACP support
|
|
1085
|
-
*/
|
|
1086
|
-
registry.register({
|
|
1087
|
-
id: 'kimi',
|
|
1088
|
-
name: 'Kimi CLI',
|
|
1089
|
-
command: 'kimi',
|
|
1090
|
-
protocol: 'acp',
|
|
1091
|
-
supportsStdin: false,
|
|
1092
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1093
|
-
buildArgs: () => ['acp'],
|
|
1094
|
-
protocolHandler: acpProtocolHandler
|
|
1095
|
-
});
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* Qwen Code Agent (Alibaba) - Native ACP support
|
|
1099
|
-
*/
|
|
1100
|
-
registry.register({
|
|
1101
|
-
id: 'qwen',
|
|
1102
|
-
name: 'Qwen Code',
|
|
1103
|
-
command: 'qwen-code',
|
|
1104
|
-
protocol: 'acp',
|
|
1105
|
-
supportsStdin: false,
|
|
1106
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1107
|
-
buildArgs: () => ['acp'],
|
|
1108
|
-
protocolHandler: acpProtocolHandler
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
/**
|
|
1112
|
-
* Codex CLI Agent (OpenAI) - ACP support
|
|
1113
|
-
*/
|
|
1114
|
-
registry.register({
|
|
1115
|
-
id: 'codex',
|
|
1116
|
-
name: 'Codex CLI',
|
|
1117
|
-
command: 'codex',
|
|
1118
|
-
protocol: 'acp',
|
|
1119
|
-
supportsStdin: false,
|
|
1120
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1121
|
-
buildArgs: () => ['acp'],
|
|
1122
|
-
protocolHandler: acpProtocolHandler
|
|
1123
|
-
});
|
|
1124
|
-
|
|
1125
|
-
/**
|
|
1126
|
-
* Mistral Vibe Agent - Native ACP support
|
|
1127
|
-
*/
|
|
1128
|
-
registry.register({
|
|
1129
|
-
id: 'mistral',
|
|
1130
|
-
name: 'Mistral Vibe',
|
|
1131
|
-
command: 'mistral-vibe',
|
|
1132
|
-
protocol: 'acp',
|
|
1133
|
-
supportsStdin: false,
|
|
1134
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1135
|
-
buildArgs: () => ['acp'],
|
|
1136
|
-
protocolHandler: acpProtocolHandler
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
/**
|
|
1140
|
-
* Kiro CLI Agent - Native ACP support
|
|
1141
|
-
*/
|
|
1142
|
-
registry.register({
|
|
1143
|
-
id: 'kiro',
|
|
1144
|
-
name: 'Kiro CLI',
|
|
1145
|
-
command: 'kiro',
|
|
1146
|
-
protocol: 'acp',
|
|
1147
|
-
supportsStdin: false,
|
|
1148
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1149
|
-
buildArgs: () => ['acp'],
|
|
1150
|
-
protocolHandler: acpProtocolHandler
|
|
1151
|
-
});
|
|
1152
|
-
|
|
1153
|
-
/**
|
|
1154
|
-
* fast-agent - Native ACP support
|
|
1155
|
-
*/
|
|
1156
|
-
registry.register({
|
|
1157
|
-
id: 'fast-agent',
|
|
1158
|
-
name: 'fast-agent',
|
|
1159
|
-
command: 'fast-agent',
|
|
1160
|
-
protocol: 'acp',
|
|
1161
|
-
supportsStdin: false,
|
|
1162
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
1163
|
-
buildArgs: () => ['acp'],
|
|
1164
|
-
protocolHandler: acpProtocolHandler
|
|
1165
|
-
});
|
|
1166
|
-
|
|
1167
|
-
/**
|
|
1168
|
-
* Kilo CLI Agent (OpenCode fork)
|
|
1169
|
-
* Built on OpenCode, supports ACP protocol
|
|
1170
|
-
* Uses 'kilo' command - installed via npm install -g @kilocode/cli
|
|
1171
|
-
*/
|
|
1172
|
-
registry.register({
|
|
1173
|
-
id: 'kilo',
|
|
1174
|
-
name: 'Kilo CLI',
|
|
1175
|
-
command: 'kilo',
|
|
1176
|
-
protocol: 'acp',
|
|
1177
|
-
supportsStdin: false,
|
|
1178
|
-
npxPackage: '@kilocode/cli',
|
|
1179
|
-
supportedFeatures: ['streaming', 'resume', 'acp-protocol', 'models'],
|
|
1180
|
-
|
|
1181
|
-
buildArgs(prompt, config) {
|
|
1182
|
-
return ['acp'];
|
|
1183
|
-
},
|
|
1184
|
-
|
|
1185
|
-
protocolHandler(message, context) {
|
|
1186
|
-
return acpProtocolHandler(message, context);
|
|
1187
|
-
}
|
|
1188
|
-
});
|
|
1189
|
-
|
|
1190
|
-
/**
|
|
1191
|
-
* Main export function - runs any registered agent
|
|
1192
|
-
*/
|
|
1193
|
-
export async function runClaudeWithStreaming(prompt, cwd, agentId = 'claude-code', config = {}) {
|
|
1194
|
-
prompt = typeof prompt === 'string' ? prompt : (prompt ? JSON.stringify(prompt) : '');
|
|
1195
|
-
const agent = registry.get(agentId);
|
|
1196
|
-
|
|
1197
|
-
if (!agent) {
|
|
1198
|
-
throw new Error(`Unknown agent: ${agentId}. Registered agents: ${registry.list().map(a => a.id).join(', ')}`);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
const enhancedConfig = { ...config };
|
|
1202
|
-
if (!enhancedConfig.systemPrompt) {
|
|
1203
|
-
enhancedConfig.systemPrompt = '';
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
// Append communication guidelines for all agents
|
|
1207
|
-
const communicationGuidelines = `
|
|
1208
|
-
RESPONSE FORMAT: Respond in short, plain text sentences only. No markdown. No bullet points. No bold or italic text. No headers. No numbered lists. No code blocks in prose responses. Write as if speaking aloud. Keep responses concise and conversational. Only share what the user needs to know: errors, required actions, or direct answers. Do not narrate progress or summarize completed steps.
|
|
1209
|
-
`;
|
|
1210
|
-
|
|
1211
|
-
if (!enhancedConfig.systemPrompt.includes('RESPONSE FORMAT')) {
|
|
1212
|
-
enhancedConfig.systemPrompt = communicationGuidelines + enhancedConfig.systemPrompt;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
if (agentId && agentId !== 'claude-code') {
|
|
1216
|
-
const displayAgentId = agentId.split('-·-')[0];
|
|
1217
|
-
const agentPrefix = `use ${displayAgentId} subagent to. `;
|
|
1218
|
-
if (!enhancedConfig.systemPrompt.includes(agentPrefix)) {
|
|
1219
|
-
enhancedConfig.systemPrompt = agentPrefix + enhancedConfig.systemPrompt;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
return agent.run(prompt, cwd, enhancedConfig);
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/**
|
|
1227
|
-
* Get list of registered agents
|
|
1228
|
-
*/
|
|
1229
|
-
export function getRegisteredAgents() {
|
|
1230
|
-
return registry.list();
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
/**
|
|
1234
|
-
* Get list of installed/available agents
|
|
1235
|
-
*/
|
|
1236
|
-
export function getAvailableAgents() {
|
|
1237
|
-
return registry.listACPAvailable();
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
/**
|
|
1241
|
-
* Check if an agent is registered
|
|
1242
|
-
*/
|
|
1243
|
-
export function isAgentRegistered(agentId) {
|
|
1244
|
-
return registry.has(agentId);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
export default runClaudeWithStreaming;
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import { registerAllAgents } from './agent-registry-configs.js';
|
|
4
|
+
import { runACPWithRetry } from './acp-runner.js';
|
|
5
|
+
|
|
6
|
+
const isWindows = process.platform === 'win32';
|
|
7
|
+
|
|
8
|
+
function getSpawnOptions(cwd, additionalOptions = {}) {
|
|
9
|
+
const options = { cwd, windowsHide: true, ...additionalOptions };
|
|
10
|
+
if (isWindows) options.shell = true;
|
|
11
|
+
if (!options.env) options.env = { ...process.env };
|
|
12
|
+
delete options.env.CLAUDECODE;
|
|
13
|
+
return options;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolveCommand(command, npxPackage) {
|
|
17
|
+
const whichCmd = isWindows ? 'where' : 'which';
|
|
18
|
+
const check = spawnSync(whichCmd, [command], { encoding: 'utf-8', timeout: 3000 });
|
|
19
|
+
if (check.status === 0 && (check.stdout || '').trim()) return { cmd: command, prefixArgs: [] };
|
|
20
|
+
if (npxPackage) {
|
|
21
|
+
const npxCheck = spawnSync(whichCmd, ['npx'], { encoding: 'utf-8', timeout: 3000 });
|
|
22
|
+
if (npxCheck.status === 0) return { cmd: 'npx', prefixArgs: ['--yes', npxPackage] };
|
|
23
|
+
const bunCheck = spawnSync(whichCmd, ['bun'], { encoding: 'utf-8', timeout: 3000 });
|
|
24
|
+
if (bunCheck.status === 0) return { cmd: 'bun', prefixArgs: ['x', npxPackage] };
|
|
25
|
+
}
|
|
26
|
+
return { cmd: command, prefixArgs: [] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class AgentRunner {
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.id = config.id;
|
|
32
|
+
this.name = config.name;
|
|
33
|
+
this.command = config.command;
|
|
34
|
+
this.protocol = config.protocol || 'direct';
|
|
35
|
+
this.buildArgs = config.buildArgs || this.defaultBuildArgs;
|
|
36
|
+
this.parseOutput = config.parseOutput || this.defaultParseOutput;
|
|
37
|
+
this.supportsStdin = config.supportsStdin ?? true;
|
|
38
|
+
this.closeStdin = config.closeStdin ?? false;
|
|
39
|
+
this.supportedFeatures = config.supportedFeatures || [];
|
|
40
|
+
this.protocolHandler = config.protocolHandler || null;
|
|
41
|
+
this.requiresAdapter = config.requiresAdapter || false;
|
|
42
|
+
this.adapterCommand = config.adapterCommand || null;
|
|
43
|
+
this.adapterArgs = config.adapterArgs || [];
|
|
44
|
+
this.npxPackage = config.npxPackage || null;
|
|
45
|
+
this.spawnEnv = config.spawnEnv || {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
defaultBuildArgs() { return []; }
|
|
49
|
+
defaultParseOutput(line) { try { return JSON.parse(line); } catch { return null; } }
|
|
50
|
+
|
|
51
|
+
async run(prompt, cwd, config = {}) {
|
|
52
|
+
if (this.protocol === 'acp' && this.protocolHandler) return this.runACP(prompt, cwd, config);
|
|
53
|
+
return this.runDirect(prompt, cwd, config);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async runDirect(prompt, cwd, config = {}) {
|
|
57
|
+
const { timeout = 300000, onEvent = null, onError = null, onRateLimit = null } = config;
|
|
58
|
+
if (process.env.DEBUG === '1') {
|
|
59
|
+
const sp = config.systemPrompt;
|
|
60
|
+
console.error(`[prompt-trace] convId=${config.conversationId} promptType=${typeof prompt} promptLen=${String(prompt).length} prompt0=${String(prompt).slice(0, 100)} sysLen=${sp ? String(sp).length : 0}`);
|
|
61
|
+
}
|
|
62
|
+
const args = this.buildArgs(prompt, config);
|
|
63
|
+
const spawnOpts = getSpawnOptions(cwd);
|
|
64
|
+
if (Object.keys(this.spawnEnv).length > 0) {
|
|
65
|
+
spawnOpts.env = { ...spawnOpts.env, ...this.spawnEnv };
|
|
66
|
+
for (const [k, v] of Object.entries(this.spawnEnv)) { if (v === undefined) delete spawnOpts.env[k]; }
|
|
67
|
+
}
|
|
68
|
+
if (cwd) spawnOpts.env.CLAUDE_PROJECT_DIR = cwd;
|
|
69
|
+
const proc = execa(this.command, args, { cwd, env: spawnOpts.env, stdin: this.closeStdin ? 'ignore' : 'pipe', stdout: 'pipe', stderr: 'pipe', reject: false, timeout, windowsHide: true, shell: isWindows });
|
|
70
|
+
console.log(`[${this.id}] Spawned PID ${proc.pid} closeStdin=${this.closeStdin}`);
|
|
71
|
+
if (config.onPid) { try { config.onPid(proc.pid); } catch (e) {} }
|
|
72
|
+
if (config.onProcess) { try { config.onProcess(proc); } catch (e) {} }
|
|
73
|
+
if (this.supportsStdin && this.stdinPrompt && proc.stdin) {
|
|
74
|
+
proc.stdin.write(typeof prompt === 'string' ? prompt : String(prompt));
|
|
75
|
+
}
|
|
76
|
+
const outputs = [];
|
|
77
|
+
let sessionId = null, rateLimited = false, retryAfterSec = 60, authError = false, authErrorMessage = '', stderrBuffer = '';
|
|
78
|
+
|
|
79
|
+
proc.stderr.on('data', (chunk) => {
|
|
80
|
+
const errorText = chunk.toString();
|
|
81
|
+
stderrBuffer += errorText;
|
|
82
|
+
console.error(`[${this.id}] stderr:`, errorText);
|
|
83
|
+
if (errorText.match(/401|unauthorized|invalid.*auth|invalid.*token|auth.*failed|permission denied|access denied/i)) { authError = true; authErrorMessage = errorText.trim(); }
|
|
84
|
+
if (errorText.match(/rate.?limit|429|too many requests|overloaded|throttl|hit your limit/i)) {
|
|
85
|
+
rateLimited = true;
|
|
86
|
+
const retryMatch = errorText.match(/retry.?after[:\s]+(\d+)/i);
|
|
87
|
+
if (retryMatch) { retryAfterSec = parseInt(retryMatch[1], 10) || 60; }
|
|
88
|
+
else {
|
|
89
|
+
const resetTimeMatch = errorText.match(/resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*\(?(UTC|[A-Z]{2,4})\)?/i);
|
|
90
|
+
if (resetTimeMatch) {
|
|
91
|
+
let hours = parseInt(resetTimeMatch[1], 10);
|
|
92
|
+
const minutes = resetTimeMatch[2] ? parseInt(resetTimeMatch[2], 10) : 0;
|
|
93
|
+
const period = resetTimeMatch[3]?.toLowerCase();
|
|
94
|
+
if (period === 'pm' && hours !== 12) hours += 12;
|
|
95
|
+
if (period === 'am' && hours === 12) hours = 0;
|
|
96
|
+
const now = new Date(), resetTime = new Date(now);
|
|
97
|
+
resetTime.setUTCHours(hours, minutes, 0, 0);
|
|
98
|
+
if (resetTime <= now) resetTime.setUTCDate(resetTime.getUTCDate() + 1);
|
|
99
|
+
retryAfterSec = Math.max(60, Math.ceil((resetTime.getTime() - now.getTime()) / 1000));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (onError) { try { onError(errorText); } catch (e) {} }
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
let jsonBuffer = '';
|
|
107
|
+
proc.stdout.on('data', (chunk) => {
|
|
108
|
+
jsonBuffer += chunk.toString();
|
|
109
|
+
const lines = jsonBuffer.split('\n');
|
|
110
|
+
jsonBuffer = lines.pop();
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
if (!line.trim()) continue;
|
|
113
|
+
const parsed = this.parseOutput(line);
|
|
114
|
+
if (!parsed) continue;
|
|
115
|
+
outputs.push(parsed);
|
|
116
|
+
if (parsed.session_id) sessionId = parsed.session_id;
|
|
117
|
+
if (onEvent) { try { onEvent(parsed); } catch (e) { console.error(`[${this.id}] onEvent error: ${e.message}`); } }
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const result = await proc;
|
|
122
|
+
if (proc.stdin && !proc.stdin.destroyed) { try { proc.stdin.end(); } catch (e) {} }
|
|
123
|
+
if (jsonBuffer.trim()) {
|
|
124
|
+
const parsed = this.parseOutput(jsonBuffer);
|
|
125
|
+
if (parsed) { outputs.push(parsed); if (parsed.session_id) sessionId = parsed.session_id; if (onEvent) { try { onEvent(parsed); } catch (e) {} } }
|
|
126
|
+
}
|
|
127
|
+
if (result.timedOut) throw new Error(`${this.name} timeout after ${timeout}ms`);
|
|
128
|
+
if (authError) { const err = new Error(`Authentication failed: ${authErrorMessage || 'Invalid credentials or unauthorized access'}`); err.authError = true; err.nonRetryable = true; throw err; }
|
|
129
|
+
if (rateLimited) { const err = new Error(`Rate limited - retry after ${retryAfterSec}s`); err.rateLimited = true; err.retryAfterSec = retryAfterSec; if (onRateLimit) { try { onRateLimit({ retryAfterSec }); } catch (e) {} } throw err; }
|
|
130
|
+
const code = result.exitCode;
|
|
131
|
+
if (code === 0 || outputs.length > 0) return { outputs, sessionId };
|
|
132
|
+
const stderrHint = stderrBuffer.trim() ? `: ${stderrBuffer.trim().slice(0, 200)}` : '';
|
|
133
|
+
const codeHint = code === 143 ? ' (SIGTERM - process was killed)' : code === 137 ? ' (SIGKILL - out of memory or force-killed)' : '';
|
|
134
|
+
throw new Error(`${this.name} exited with code ${code}${codeHint}${stderrHint}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async runACP(prompt, cwd, config = {}, _retryCount = 0) {
|
|
138
|
+
return runACPWithRetry(this, prompt, cwd, config, _retryCount);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
class AgentRegistry {
|
|
143
|
+
constructor() { this.agents = new Map(); }
|
|
144
|
+
register(config) { const runner = new AgentRunner(config); this.agents.set(config.id, runner); return runner; }
|
|
145
|
+
get(agentId) { return this.agents.get(agentId); }
|
|
146
|
+
has(agentId) { return this.agents.has(agentId); }
|
|
147
|
+
list() {
|
|
148
|
+
return Array.from(this.agents.values()).map(a => ({ id: a.id, name: a.name, command: a.command, protocol: a.protocol, requiresAdapter: a.requiresAdapter, supportedFeatures: a.supportedFeatures, npxPackage: a.npxPackage }));
|
|
149
|
+
}
|
|
150
|
+
listACPAvailable() {
|
|
151
|
+
return this.list().filter(agent => {
|
|
152
|
+
try {
|
|
153
|
+
const whichCmd = isWindows ? 'where' : 'which';
|
|
154
|
+
const which = spawnSync(whichCmd, [agent.command], { encoding: 'utf-8', timeout: 3000 });
|
|
155
|
+
if (which.status === 0) {
|
|
156
|
+
const binPath = (which.stdout || '').trim().split('\n')[0].trim();
|
|
157
|
+
if (binPath) { const check = spawnSync(binPath, ['--version'], { encoding: 'utf-8', timeout: 10000, shell: isWindows }); if (check.status === 0 && (check.stdout || '').trim().length > 0) return true; }
|
|
158
|
+
}
|
|
159
|
+
const a = this.agents.get(agent.id);
|
|
160
|
+
if (a && a.npxPackage) { const npxCheck = spawnSync(whichCmd, ['npx'], { encoding: 'utf-8', timeout: 3000 }); if (npxCheck.status === 0) return true; const bunCheck = spawnSync(whichCmd, ['bun'], { encoding: 'utf-8', timeout: 3000 }); if (bunCheck.status === 0) return true; }
|
|
161
|
+
return false;
|
|
162
|
+
} catch { return false; }
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const registry = new AgentRegistry();
|
|
168
|
+
registerAllAgents(registry);
|
|
169
|
+
|
|
170
|
+
export async function runClaudeWithStreaming(prompt, cwd, agentId = 'claude-code', config = {}) {
|
|
171
|
+
prompt = typeof prompt === 'string' ? prompt : (prompt ? JSON.stringify(prompt) : '');
|
|
172
|
+
const agent = registry.get(agentId);
|
|
173
|
+
if (!agent) throw new Error(`Unknown agent: ${agentId}. Registered agents: ${registry.list().map(a => a.id).join(', ')}`);
|
|
174
|
+
const enhancedConfig = { ...config };
|
|
175
|
+
if (!enhancedConfig.systemPrompt) enhancedConfig.systemPrompt = '';
|
|
176
|
+
const communicationGuidelines = `\nRESPONSE FORMAT: Respond in short, plain text sentences only. No markdown. No bullet points. No bold or italic text. No headers. No numbered lists. No code blocks in prose responses. Write as if speaking aloud. Keep responses concise and conversational. Only share what the user needs to know: errors, required actions, or direct answers. Do not narrate progress or summarize completed steps.\n`;
|
|
177
|
+
if (!enhancedConfig.systemPrompt.includes('RESPONSE FORMAT')) enhancedConfig.systemPrompt = communicationGuidelines + enhancedConfig.systemPrompt;
|
|
178
|
+
if (agentId && agentId !== 'claude-code') {
|
|
179
|
+
const displayAgentId = agentId.split('-·-')[0];
|
|
180
|
+
const agentPrefix = `use ${displayAgentId} subagent to. `;
|
|
181
|
+
if (!enhancedConfig.systemPrompt.includes(agentPrefix)) enhancedConfig.systemPrompt = agentPrefix + enhancedConfig.systemPrompt;
|
|
182
|
+
}
|
|
183
|
+
return agent.run(prompt, cwd, enhancedConfig);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function getRegisteredAgents() { return registry.list(); }
|
|
187
|
+
export function getAvailableAgents() { return registry.listACPAvailable(); }
|
|
188
|
+
export function isAgentRegistered(agentId) { return registry.has(agentId); }
|
|
189
|
+
export default runClaudeWithStreaming;
|