agentgui 1.0.242 → 1.0.244

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.
@@ -197,7 +197,7 @@ class AgentRunner {
197
197
  try {
198
198
  return await this._runACPOnce(prompt, cwd, config);
199
199
  } catch (err) {
200
- const isEmptyExit = err.message && err.message.includes('ACP exited with code');
200
+ const isEmptyExit = err.isPrematureEnd || (err.message && err.message.includes('ACP exited with code'));
201
201
  const isBinaryError = err.code === 'ENOENT' || (err.message && err.message.includes('ENOENT'));
202
202
  if ((isEmptyExit || isBinaryError) && _retryCount < maxRetries) {
203
203
  const delay = Math.min(1000 * Math.pow(2, _retryCount), 5000);
@@ -205,6 +205,13 @@ class AgentRunner {
205
205
  await new Promise(r => setTimeout(r, delay));
206
206
  return this.runACP(prompt, cwd, config, _retryCount + 1);
207
207
  }
208
+ if (err.isPrematureEnd) {
209
+ const premErr = new Error(err.message);
210
+ premErr.isPrematureEnd = true;
211
+ premErr.exitCode = err.exitCode;
212
+ premErr.stderrText = err.stderrText;
213
+ throw premErr;
214
+ }
208
215
  throw err;
209
216
  }
210
217
  }
@@ -437,7 +444,11 @@ class AgentRunner {
437
444
  resolve({ outputs, sessionId });
438
445
  } else {
439
446
  const detail = stderrText ? `: ${stderrText.substring(0, 200)}` : '';
440
- reject(new Error(`${this.name} ACP exited with code ${code}${detail}`));
447
+ const err = new Error(`${this.name} ACP exited with code ${code}${detail}`);
448
+ err.isPrematureEnd = true;
449
+ err.exitCode = code;
450
+ err.stderrText = stderrText;
451
+ reject(err);
441
452
  }
442
453
  });
443
454
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.242",
3
+ "version": "1.0.244",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -35,62 +35,71 @@ const modelDownloadState = {
35
35
  complete: false
36
36
  };
37
37
 
38
+ function broadcastModelProgress(progress) {
39
+ modelDownloadState.progress = progress;
40
+ broadcastSync({ type: 'model_download_progress', progress });
41
+ }
42
+
38
43
  async function ensureModelsDownloaded() {
39
- const { createRequire: cr } = await import('module');
40
- const r = cr(import.meta.url);
41
- const models = r('sttttsmodels');
42
- const checkAllFilesExist = models.checkAllFilesExist;
43
- const downloadModels = models.downloadModels;
44
-
45
- if (checkAllFilesExist && checkAllFilesExist()) {
46
- modelDownloadState.complete = true;
47
- return true;
48
- }
49
-
50
- if (!downloadModels) {
51
- console.log('[MODELS] Download function not available, skipping');
52
- modelDownloadState.complete = true;
53
- return true;
54
- }
55
-
56
44
  if (modelDownloadState.downloading) {
57
- // Wait for current download
58
45
  while (modelDownloadState.downloading) {
59
46
  await new Promise(r => setTimeout(r, 100));
60
47
  }
61
48
  return modelDownloadState.complete;
62
49
  }
63
-
64
- modelDownloadState.downloading = true;
65
- modelDownloadState.error = null;
66
-
50
+
67
51
  try {
68
- await downloadModels((progress) => {
69
- modelDownloadState.progress = progress;
70
- // Broadcast progress to all connected clients
71
- broadcastSync({
72
- type: 'model_download_progress',
73
- progress: {
74
- started: progress.started,
75
- done: progress.done,
76
- error: progress.error,
77
- downloading: progress.downloading,
78
- type: progress.type,
79
- completedFiles: progress.completedFiles,
80
- totalFiles: progress.totalFiles,
81
- totalDownloaded: progress.totalDownloaded,
82
- totalBytes: progress.totalBytes
83
- }
84
- });
85
- });
52
+ const { createRequire: cr } = await import('module');
53
+ const r = cr(import.meta.url);
54
+ const sttttsmodels = r('sttttsmodels');
55
+ const { sttDir, ttsDir } = sttttsmodels;
56
+
57
+ const sttOk = fs.existsSync(sttDir) && fs.readdirSync(sttDir).length > 0;
58
+ const ttsOk = fs.existsSync(ttsDir) && fs.readdirSync(ttsDir).length > 0;
59
+
60
+ if (sttOk && ttsOk) {
61
+ console.log('[MODELS] All model files present');
62
+ modelDownloadState.complete = true;
63
+ return true;
64
+ }
65
+
66
+ modelDownloadState.downloading = true;
67
+ modelDownloadState.error = null;
68
+
69
+ const webtalkWhisper = r('webtalk/whisper-models');
70
+ const webtalkTTS = r('webtalk/tts-models');
71
+ const { createConfig } = r('webtalk/config');
72
+ const config = createConfig({ sdkDir: path.dirname(fileURLToPath(import.meta.url)) });
73
+ config.modelsDir = path.dirname(sttDir);
74
+ config.ttsModelsDir = ttsDir;
75
+ config.sttModelsDir = sttDir;
76
+ config.whisperBaseUrl = 'https://huggingface.co/onnx-community/whisper-base/resolve/main/';
77
+ config.ttsBaseUrl = 'https://huggingface.co/datasets/AnEntrypoint/sttttsmodels/resolve/main/tts/';
78
+
79
+ const totalFiles = 16;
80
+ let completedFiles = 0;
81
+
82
+ if (!sttOk) {
83
+ console.log('[MODELS] Downloading STT model...');
84
+ broadcastModelProgress({ started: true, done: false, downloading: true, type: 'stt', completedFiles, totalFiles });
85
+ await webtalkWhisper.ensureModel('whisper-base', config);
86
+ completedFiles += 10;
87
+ }
88
+
89
+ if (!ttsOk) {
90
+ console.log('[MODELS] Downloading TTS models...');
91
+ broadcastModelProgress({ started: true, done: false, downloading: true, type: 'tts', completedFiles, totalFiles });
92
+ await webtalkTTS.ensureTTSModels(config);
93
+ completedFiles += 6;
94
+ }
95
+
86
96
  modelDownloadState.complete = true;
97
+ broadcastModelProgress({ started: true, done: true, downloading: false, completedFiles: totalFiles, totalFiles });
87
98
  return true;
88
99
  } catch (err) {
100
+ console.error('[MODELS] Download error:', err.message);
89
101
  modelDownloadState.error = err.message;
90
- broadcastSync({
91
- type: 'model_download_progress',
92
- progress: { error: err.message, done: true }
93
- });
102
+ broadcastModelProgress({ done: true, error: err.message });
94
103
  return false;
95
104
  } finally {
96
105
  modelDownloadState.downloading = false;
@@ -327,12 +336,57 @@ const AGENT_DEFAULT_MODELS = {
327
336
  ]
328
337
  };
329
338
 
339
+ async function fetchClaudeModelsFromAPI() {
340
+ const apiKey = process.env.ANTHROPIC_API_KEY;
341
+ if (!apiKey) return null;
342
+ try {
343
+ const https = await import('https');
344
+ return new Promise((resolve) => {
345
+ const req = https.default.request({
346
+ hostname: 'api.anthropic.com', path: '/v1/models', method: 'GET',
347
+ headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
348
+ timeout: 8000
349
+ }, (res) => {
350
+ let body = '';
351
+ res.on('data', d => body += d);
352
+ res.on('end', () => {
353
+ try {
354
+ const data = JSON.parse(body);
355
+ const items = (data.data || []).filter(m => m.id && m.id.startsWith('claude-'));
356
+ if (items.length === 0) return resolve(null);
357
+ const models = [{ id: '', label: 'Default' }];
358
+ for (const m of items) {
359
+ const label = m.display_name || m.id.replace(/^claude-/, '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
360
+ models.push({ id: m.id, label });
361
+ }
362
+ resolve(models);
363
+ } catch { resolve(null); }
364
+ });
365
+ });
366
+ req.on('error', () => resolve(null));
367
+ req.on('timeout', () => { req.destroy(); resolve(null); });
368
+ req.end();
369
+ });
370
+ } catch { return null; }
371
+ }
372
+
330
373
  async function getModelsForAgent(agentId) {
331
374
  const cached = modelCache.get(agentId);
332
- if (cached && Date.now() - cached.timestamp < 300000) {
375
+ if (cached && Date.now() - cached.timestamp < 3600000) {
333
376
  return cached.models;
334
377
  }
335
378
 
379
+ if (agentId === 'claude-code') {
380
+ const apiModels = await fetchClaudeModelsFromAPI();
381
+ if (apiModels) {
382
+ modelCache.set(agentId, { models: apiModels, timestamp: Date.now() });
383
+ return apiModels;
384
+ }
385
+ const models = AGENT_DEFAULT_MODELS[agentId];
386
+ modelCache.set(agentId, { models, timestamp: Date.now() });
387
+ return models;
388
+ }
389
+
336
390
  if (AGENT_DEFAULT_MODELS[agentId]) {
337
391
  const models = AGENT_DEFAULT_MODELS[agentId];
338
392
  modelCache.set(agentId, { models, timestamp: Date.now() });
@@ -2975,6 +3029,9 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
2975
3029
  sessionId,
2976
3030
  conversationId,
2977
3031
  error: error.message,
3032
+ isPrematureEnd: error.isPrematureEnd || false,
3033
+ exitCode: error.exitCode,
3034
+ stderrText: error.stderrText,
2978
3035
  recoverable: elapsed < 60000,
2979
3036
  timestamp: Date.now()
2980
3037
  });
package/static/index.html CHANGED
@@ -48,6 +48,8 @@
48
48
  --block-color-7: #f43f5e;
49
49
  --block-color-8: #22c55e;
50
50
  --block-color-9: #eab308;
51
+ --block-color-10: #0ea5e9;
52
+ --block-color-11: #d946ef;
51
53
  }
52
54
 
53
55
  html.dark {
@@ -66,6 +68,8 @@
66
68
  --block-color-7: #fb7185;
67
69
  --block-color-8: #4ade80;
68
70
  --block-color-9: #facc15;
71
+ --block-color-10: #38bdf8;
72
+ --block-color-11: #e879f9;
69
73
  }
70
74
 
71
75
  html, body {
@@ -793,6 +793,20 @@ class AgentGUIClient {
793
793
  // Stop polling for chunks
794
794
  this.stopChunkPolling();
795
795
 
796
+ // If this is a premature ACP end, render distinct warning block
797
+ if (data.isPrematureEnd) {
798
+ this.renderer.queueEvent({
799
+ type: 'streaming_error',
800
+ isPrematureEnd: true,
801
+ exitCode: data.exitCode,
802
+ error: data.error,
803
+ stderrText: data.stderrText,
804
+ sessionId: data.sessionId,
805
+ conversationId: data.conversationId,
806
+ timestamp: data.timestamp || Date.now()
807
+ });
808
+ }
809
+
796
810
  const sessionId = data.sessionId || this.state.currentSession?.id;
797
811
  const streamingEl = document.getElementById(`streaming-${sessionId}`);
798
812
  if (streamingEl) {
@@ -300,6 +300,10 @@ class StreamingRenderer {
300
300
  return this.renderBlock(event.block, event);
301
301
  }
302
302
 
303
+ if (event.type === 'streaming_error' && event.isPrematureEnd) {
304
+ return this.renderBlockPremature({ type: 'premature', error: event.error, exitCode: event.exitCode });
305
+ }
306
+
303
307
  switch (event.type) {
304
308
  case 'streaming_start':
305
309
  return this.renderStreamingStart(event);
@@ -366,6 +370,8 @@ class StreamingRenderer {
366
370
  return this.renderBlockUsage(block, context);
367
371
  case 'plan':
368
372
  return this.renderBlockPlan(block, context);
373
+ case 'premature':
374
+ return this.renderBlockPremature(block, context);
369
375
  default:
370
376
  return this.renderBlockGeneric(block, context);
371
377
  }
@@ -415,7 +421,10 @@ class StreamingRenderer {
415
421
  'system': 6,
416
422
  'result': 7,
417
423
  'error': 8,
418
- 'image': 9
424
+ 'image': 9,
425
+ 'plan': 10,
426
+ 'usage': 11,
427
+ 'premature': 8
419
428
  };
420
429
  return typeColors[blockType] !== undefined ? typeColors[blockType] : 0;
421
430
  }
@@ -465,6 +474,7 @@ class StreamingRenderer {
465
474
  renderBlockCode(block, context) {
466
475
  const div = document.createElement('div');
467
476
  div.className = 'block-code';
477
+ div.style.borderLeft = `3px solid var(--block-color-${this._getBlockColorIndex('code')})`;
468
478
 
469
479
  const code = block.code || '';
470
480
  const language = (block.language || 'plaintext').toLowerCase();
@@ -510,6 +520,8 @@ class StreamingRenderer {
510
520
  renderBlockThinking(block, context) {
511
521
  const div = document.createElement('div');
512
522
  div.className = 'block-thinking';
523
+ const colorIndex = this._getBlockColorIndex('thinking');
524
+ div.style.borderLeft = `3px solid var(--block-color-${colorIndex})`;
513
525
 
514
526
  const thinking = block.thinking || '';
515
527
  div.innerHTML = `
@@ -1240,6 +1252,7 @@ class StreamingRenderer {
1240
1252
  renderBlockImage(block, context) {
1241
1253
  const div = document.createElement('div');
1242
1254
  div.className = 'block-image';
1255
+ div.style.borderLeft = `3px solid var(--block-color-${this._getBlockColorIndex('image')})`;
1243
1256
 
1244
1257
  let src = block.image || block.src || '';
1245
1258
  const alt = block.alt || 'Image';
@@ -1263,6 +1276,8 @@ class StreamingRenderer {
1263
1276
  renderBlockBash(block, context) {
1264
1277
  const div = document.createElement('div');
1265
1278
  div.className = 'block-bash';
1279
+ const colorIndex = this._getBlockColorIndex('bash');
1280
+ div.style.borderLeft = `3px solid var(--block-color-${colorIndex})`;
1266
1281
 
1267
1282
  const command = block.command || block.code || '';
1268
1283
  const output = block.output || '';
@@ -1290,6 +1305,7 @@ class StreamingRenderer {
1290
1305
  const details = document.createElement('details');
1291
1306
  details.className = 'folded-tool folded-tool-info';
1292
1307
  details.dataset.eventType = 'system';
1308
+ details.style.borderLeft = `3px solid var(--block-color-${this._getBlockColorIndex('system')})`;
1293
1309
  const desc = block.model ? this.escapeHtml(block.model) : 'Session';
1294
1310
  const summary = document.createElement('summary');
1295
1311
  summary.className = 'folded-tool-bar';
@@ -1326,6 +1342,9 @@ class StreamingRenderer {
1326
1342
  const details = document.createElement('details');
1327
1343
  details.className = isError ? 'folded-tool folded-tool-error' : 'folded-tool';
1328
1344
  details.dataset.eventType = 'result';
1345
+ if (!isError) details.setAttribute('open', '');
1346
+ const colorIndex = this._getBlockColorIndex(isError ? 'error' : 'result');
1347
+ details.style.borderLeft = `3px solid var(--block-color-${colorIndex})`;
1329
1348
 
1330
1349
  const iconSvg = isError
1331
1350
  ? '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
@@ -1380,6 +1399,7 @@ class StreamingRenderer {
1380
1399
  const div = document.createElement('div');
1381
1400
  div.className = 'block-tool-status';
1382
1401
  div.dataset.toolUseId = block.tool_use_id || '';
1402
+ div.style.borderLeft = `3px solid var(--block-color-${this._getBlockColorIndex('tool_use')})`;
1383
1403
  div.innerHTML = `
1384
1404
  <div style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0.5rem;font-size:0.75rem;color:var(--color-text-secondary)">
1385
1405
  ${statusIcons[status] || statusIcons.pending}
@@ -1400,6 +1420,7 @@ class StreamingRenderer {
1400
1420
 
1401
1421
  const div = document.createElement('div');
1402
1422
  div.className = 'block-usage';
1423
+ div.style.borderLeft = `3px solid var(--block-color-${this._getBlockColorIndex('usage')})`;
1403
1424
  div.innerHTML = `
1404
1425
  <div style="display:flex;gap:1rem;padding:0.25rem 0.5rem;font-size:0.7rem;color:var(--color-text-secondary);background:var(--color-bg-secondary);border-radius:0.25rem">
1405
1426
  ${used ? `<span><strong>Used:</strong> ${used.toLocaleString()}</span>` : ''}
@@ -1430,6 +1451,7 @@ class StreamingRenderer {
1430
1451
 
1431
1452
  const div = document.createElement('div');
1432
1453
  div.className = 'block-plan';
1454
+ div.style.borderLeft = `3px solid var(--block-color-${this._getBlockColorIndex('plan')})`;
1433
1455
  div.innerHTML = `
1434
1456
  <details class="folded-tool folded-tool-info">
1435
1457
  <summary class="folded-tool-bar">
@@ -1452,6 +1474,21 @@ class StreamingRenderer {
1452
1474
  return div;
1453
1475
  }
1454
1476
 
1477
+ renderBlockPremature(block, context) {
1478
+ const div = document.createElement('div');
1479
+ div.className = 'folded-tool folded-tool-error block-premature';
1480
+ div.style.borderLeft = `3px solid var(--block-color-${this._getBlockColorIndex('premature')})`;
1481
+ const code = block.exitCode != null ? ` (exit ${block.exitCode})` : '';
1482
+ div.innerHTML = `
1483
+ <div class="folded-tool-bar" style="background:rgba(245,158,11,0.1)">
1484
+ <span class="folded-tool-icon" style="color:#f59e0b"><svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg></span>
1485
+ <span class="folded-tool-name" style="color:#f59e0b">ACP Ended Prematurely${this.escapeHtml(code)}</span>
1486
+ <span class="folded-tool-desc">${this.escapeHtml(block.error || 'Process exited without output')}</span>
1487
+ </div>
1488
+ `;
1489
+ return div;
1490
+ }
1491
+
1455
1492
  /**
1456
1493
  * Render generic block with formatted key-value pairs
1457
1494
  */