agentgui 1.0.234 → 1.0.236

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.
@@ -561,11 +561,33 @@ registry.register({
561
561
 
562
562
  // Agent message chunk (text response)
563
563
  if (update.sessionUpdate === 'agent_message_chunk' && update.content) {
564
+ let contentBlock;
565
+
566
+ // Handle different content formats
567
+ if (typeof update.content === 'string') {
568
+ contentBlock = { type: 'text', text: update.content };
569
+ } else if (update.content.type === 'text' && update.content.text) {
570
+ contentBlock = update.content;
571
+ } else if (update.content.text) {
572
+ contentBlock = { type: 'text', text: update.content.text };
573
+ } else if (update.content.content) {
574
+ const inner = update.content.content;
575
+ if (typeof inner === 'string') {
576
+ contentBlock = { type: 'text', text: inner };
577
+ } else if (inner.type === 'text' && inner.text) {
578
+ contentBlock = inner;
579
+ } else {
580
+ contentBlock = { type: 'text', text: JSON.stringify(inner) };
581
+ }
582
+ } else {
583
+ contentBlock = { type: 'text', text: JSON.stringify(update.content) };
584
+ }
585
+
564
586
  return {
565
587
  type: 'assistant',
566
588
  message: {
567
589
  role: 'assistant',
568
- content: [update.content]
590
+ content: [contentBlock]
569
591
  },
570
592
  session_id: params.sessionId
571
593
  };
@@ -580,26 +602,63 @@ registry.register({
580
602
  content: [{
581
603
  type: 'tool_use',
582
604
  id: update.toolCallId,
583
- name: update.title || 'tool',
584
- input: update.input || {}
605
+ name: update.title || update.kind || 'tool',
606
+ input: update.rawInput || update.input || {}
585
607
  }]
586
608
  },
587
609
  session_id: params.sessionId
588
610
  };
589
611
  }
590
612
 
591
- // Tool call update (result)
592
- if (update.sessionUpdate === 'tool_call_update' && update.status === 'completed') {
593
- const content = update.content && update.content[0] ? update.content[0].content : null;
613
+ // Tool call update (result) - handle all statuses
614
+ if (update.sessionUpdate === 'tool_call_update') {
615
+ const status = update.status;
616
+ const isError = status === 'failed';
617
+ const isCompleted = status === 'completed';
618
+
619
+ if (!isCompleted && !isError) {
620
+ return {
621
+ type: 'tool_status',
622
+ tool_use_id: update.toolCallId,
623
+ status: status,
624
+ session_id: params.sessionId
625
+ };
626
+ }
627
+
628
+ const contentParts = [];
629
+ if (update.content && Array.isArray(update.content)) {
630
+ for (const item of update.content) {
631
+ if (item.type === 'content' && item.content) {
632
+ const innerContent = item.content;
633
+ if (innerContent.type === 'text' && innerContent.text) {
634
+ contentParts.push(innerContent.text);
635
+ } else if (innerContent.type === 'resource' && innerContent.resource) {
636
+ contentParts.push(innerContent.resource.text || JSON.stringify(innerContent.resource));
637
+ } else {
638
+ contentParts.push(JSON.stringify(innerContent));
639
+ }
640
+ } else if (item.type === 'diff') {
641
+ const diffText = item.oldText
642
+ ? `--- ${item.path}\n+++ ${item.path}\n${item.oldText}\n---\n${item.newText}`
643
+ : `+++ ${item.path}\n${item.newText}`;
644
+ contentParts.push(diffText);
645
+ } else if (item.type === 'terminal') {
646
+ contentParts.push(`[Terminal: ${item.terminalId}]`);
647
+ }
648
+ }
649
+ }
650
+
651
+ const combinedContent = contentParts.join('\n') || (update.rawOutput ? JSON.stringify(update.rawOutput) : '');
652
+
594
653
  return {
595
- type: 'assistant',
654
+ type: 'user',
596
655
  message: {
597
- role: 'assistant',
656
+ role: 'user',
598
657
  content: [{
599
658
  type: 'tool_result',
600
659
  tool_use_id: update.toolCallId,
601
- content: content ? (content.text || JSON.stringify(content)) : '',
602
- is_error: false
660
+ content: combinedContent,
661
+ is_error: isError
603
662
  }]
604
663
  },
605
664
  session_id: params.sessionId
@@ -668,11 +727,33 @@ function createACPProtocolHandler() {
668
727
 
669
728
  // Agent message chunk (text response)
670
729
  if (update.sessionUpdate === 'agent_message_chunk' && update.content) {
730
+ let contentBlock;
731
+
732
+ // Handle different content formats
733
+ if (typeof update.content === 'string') {
734
+ contentBlock = { type: 'text', text: update.content };
735
+ } else if (update.content.type === 'text' && update.content.text) {
736
+ contentBlock = update.content;
737
+ } else if (update.content.text) {
738
+ contentBlock = { type: 'text', text: update.content.text };
739
+ } else if (update.content.content) {
740
+ const inner = update.content.content;
741
+ if (typeof inner === 'string') {
742
+ contentBlock = { type: 'text', text: inner };
743
+ } else if (inner.type === 'text' && inner.text) {
744
+ contentBlock = inner;
745
+ } else {
746
+ contentBlock = { type: 'text', text: JSON.stringify(inner) };
747
+ }
748
+ } else {
749
+ contentBlock = { type: 'text', text: JSON.stringify(update.content) };
750
+ }
751
+
671
752
  return {
672
753
  type: 'assistant',
673
754
  message: {
674
755
  role: 'assistant',
675
- content: [update.content]
756
+ content: [contentBlock]
676
757
  },
677
758
  session_id: params.sessionId
678
759
  };
@@ -687,26 +768,63 @@ function createACPProtocolHandler() {
687
768
  content: [{
688
769
  type: 'tool_use',
689
770
  id: update.toolCallId,
690
- name: update.title || 'tool',
691
- input: update.input || {}
771
+ name: update.title || update.kind || 'tool',
772
+ input: update.rawInput || update.input || {}
692
773
  }]
693
774
  },
694
775
  session_id: params.sessionId
695
776
  };
696
777
  }
697
778
 
698
- // Tool call update (result)
699
- if (update.sessionUpdate === 'tool_call_update' && update.status === 'completed') {
700
- const content = update.content && update.content[0] ? update.content[0].content : null;
779
+ // Tool call update (result) - handle all statuses
780
+ if (update.sessionUpdate === 'tool_call_update') {
781
+ const status = update.status;
782
+ const isError = status === 'failed';
783
+ const isCompleted = status === 'completed';
784
+
785
+ if (!isCompleted && !isError) {
786
+ return {
787
+ type: 'tool_status',
788
+ tool_use_id: update.toolCallId,
789
+ status: status,
790
+ session_id: params.sessionId
791
+ };
792
+ }
793
+
794
+ const contentParts = [];
795
+ if (update.content && Array.isArray(update.content)) {
796
+ for (const item of update.content) {
797
+ if (item.type === 'content' && item.content) {
798
+ const innerContent = item.content;
799
+ if (innerContent.type === 'text' && innerContent.text) {
800
+ contentParts.push(innerContent.text);
801
+ } else if (innerContent.type === 'resource' && innerContent.resource) {
802
+ contentParts.push(innerContent.resource.text || JSON.stringify(innerContent.resource));
803
+ } else {
804
+ contentParts.push(JSON.stringify(innerContent));
805
+ }
806
+ } else if (item.type === 'diff') {
807
+ const diffText = item.oldText
808
+ ? `--- ${item.path}\n+++ ${item.path}\n${item.oldText}\n---\n${item.newText}`
809
+ : `+++ ${item.path}\n${item.newText}`;
810
+ contentParts.push(diffText);
811
+ } else if (item.type === 'terminal') {
812
+ contentParts.push(`[Terminal: ${item.terminalId}]`);
813
+ }
814
+ }
815
+ }
816
+
817
+ const combinedContent = contentParts.join('\n') || (update.rawOutput ? JSON.stringify(update.rawOutput) : '');
818
+
701
819
  return {
702
- type: 'assistant',
820
+ type: 'user',
703
821
  message: {
704
- role: 'assistant',
822
+ role: 'user',
705
823
  content: [{
706
824
  type: 'tool_result',
707
825
  tool_use_id: update.toolCallId,
708
- content: content ? (content.text || JSON.stringify(content)) : '',
709
- is_error: false
826
+ content: combinedContent,
827
+ is_error: isError
710
828
  }]
711
829
  },
712
830
  session_id: params.sessionId
@@ -726,6 +844,15 @@ function createACPProtocolHandler() {
726
844
  };
727
845
  }
728
846
 
847
+ // Plan update
848
+ if (update.sessionUpdate === 'plan') {
849
+ return {
850
+ type: 'plan',
851
+ entries: update.entries || [],
852
+ session_id: params.sessionId
853
+ };
854
+ }
855
+
729
856
  return null;
730
857
  }
731
858
 
package/lib/speech.js CHANGED
@@ -89,10 +89,7 @@ function synthesize(text, voiceId) {
89
89
  function synthesizeStream(text, voiceId) {
90
90
  if (needsPatch && voiceId && PREDEFINED_IDS.has(voiceId)) {
91
91
  return (async function* () {
92
- const sentences = serverTTS.splitSentences(text);
93
- for (const sentence of sentences) {
94
- yield await synthesizeDirect(sentence, voiceId);
95
- }
92
+ yield await synthesizeDirect(text, voiceId);
96
93
  })();
97
94
  }
98
95
  return serverTTS.synthesizeStream(text, voiceId, EXTRA_VOICE_DIRS);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.234",
3
+ "version": "1.0.236",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -120,19 +120,16 @@ function flushTTSaccumulator(key, conversationId, sessionId) {
120
120
  }
121
121
  }
122
122
  if (voices.size === 0) return;
123
- const sentences = speech.splitSentences(text);
123
+ const cacheKey = speech.ttsCacheKey(text, vid);
124
124
  for (const vid of voices) {
125
- for (const sentence of sentences) {
126
- const cacheKey = speech.ttsCacheKey(sentence, vid);
127
- const cached = speech.ttsCacheGet(cacheKey);
128
- if (cached) {
129
- pushTTSAudio(cacheKey, cached, conversationId, sessionId, vid);
130
- continue;
131
- }
132
- speech.synthesize(sentence, vid).then(wav => {
133
- pushTTSAudio(cacheKey, wav, conversationId, sessionId, vid);
134
- }).catch(() => {});
125
+ const cached = speech.ttsCacheGet(cacheKey);
126
+ if (cached) {
127
+ pushTTSAudio(cacheKey, cached, conversationId, sessionId, vid);
128
+ continue;
135
129
  }
130
+ speech.synthesize(text, vid).then(wav => {
131
+ pushTTSAudio(cacheKey, wav, conversationId, sessionId, vid);
132
+ }).catch(() => {});
136
133
  }
137
134
  }).catch(() => {});
138
135
  }
@@ -2069,6 +2066,46 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
2069
2066
  if (parsed.result && allBlocks.length === 0) {
2070
2067
  allBlocks.push({ type: 'text', text: String(parsed.result) });
2071
2068
  }
2069
+ } else if (parsed.type === 'tool_status') {
2070
+ // Handle ACP tool status updates (in_progress, pending)
2071
+ broadcastSync({
2072
+ type: 'streaming_progress',
2073
+ sessionId,
2074
+ conversationId,
2075
+ block: {
2076
+ type: 'tool_status',
2077
+ tool_use_id: parsed.tool_use_id,
2078
+ status: parsed.status
2079
+ },
2080
+ seq: currentSequence,
2081
+ timestamp: Date.now()
2082
+ });
2083
+ } else if (parsed.type === 'usage') {
2084
+ // Handle ACP usage updates
2085
+ broadcastSync({
2086
+ type: 'streaming_progress',
2087
+ sessionId,
2088
+ conversationId,
2089
+ block: {
2090
+ type: 'usage',
2091
+ usage: parsed.usage
2092
+ },
2093
+ seq: currentSequence,
2094
+ timestamp: Date.now()
2095
+ });
2096
+ } else if (parsed.type === 'plan') {
2097
+ // Handle ACP plan updates
2098
+ broadcastSync({
2099
+ type: 'streaming_progress',
2100
+ sessionId,
2101
+ conversationId,
2102
+ block: {
2103
+ type: 'plan',
2104
+ entries: parsed.entries
2105
+ },
2106
+ seq: currentSequence,
2107
+ timestamp: Date.now()
2108
+ });
2072
2109
  }
2073
2110
  };
2074
2111
 
@@ -351,6 +351,12 @@ class StreamingRenderer {
351
351
  return this.renderBlockSystem(block, context);
352
352
  case 'result':
353
353
  return this.renderBlockResult(block, context);
354
+ case 'tool_status':
355
+ return this.renderBlockToolStatus(block, context);
356
+ case 'usage':
357
+ return this.renderBlockUsage(block, context);
358
+ case 'plan':
359
+ return this.renderBlockPlan(block, context);
354
360
  default:
355
361
  return this.renderBlockGeneric(block, context);
356
362
  }
@@ -1320,6 +1326,95 @@ class StreamingRenderer {
1320
1326
  return details;
1321
1327
  }
1322
1328
 
1329
+ /**
1330
+ * Render tool status block (ACP in_progress/pending updates)
1331
+ */
1332
+ renderBlockToolStatus(block, context) {
1333
+ const status = block.status || 'pending';
1334
+ const statusIcons = {
1335
+ pending: '<svg viewBox="0 0 20 20" fill="currentColor" style="color:var(--color-text-secondary)"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>',
1336
+ in_progress: '<svg viewBox="0 0 20 20" fill="currentColor" class="animate-spin" style="color:var(--color-info)"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>'
1337
+ };
1338
+ const statusLabels = {
1339
+ pending: 'Pending',
1340
+ in_progress: 'Running...'
1341
+ };
1342
+
1343
+ const div = document.createElement('div');
1344
+ div.className = 'block-tool-status';
1345
+ div.dataset.toolUseId = block.tool_use_id || '';
1346
+ div.innerHTML = `
1347
+ <div style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0.5rem;font-size:0.75rem;color:var(--color-text-secondary)">
1348
+ ${statusIcons[status] || statusIcons.pending}
1349
+ <span>${statusLabels[status] || status}</span>
1350
+ </div>
1351
+ `;
1352
+ return div;
1353
+ }
1354
+
1355
+ /**
1356
+ * Render usage block (ACP usage updates)
1357
+ */
1358
+ renderBlockUsage(block, context) {
1359
+ const usage = block.usage || {};
1360
+ const used = usage.used || 0;
1361
+ const size = usage.size || 0;
1362
+ const cost = usage.cost ? '$' + usage.cost.toFixed(4) : '';
1363
+
1364
+ const div = document.createElement('div');
1365
+ div.className = 'block-usage';
1366
+ div.innerHTML = `
1367
+ <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">
1368
+ ${used ? `<span><strong>Used:</strong> ${used.toLocaleString()}</span>` : ''}
1369
+ ${size ? `<span><strong>Context:</strong> ${size.toLocaleString()}</span>` : ''}
1370
+ ${cost ? `<span><strong>Cost:</strong> ${cost}</span>` : ''}
1371
+ </div>
1372
+ `;
1373
+ return div;
1374
+ }
1375
+
1376
+ /**
1377
+ * Render plan block (ACP plan updates)
1378
+ */
1379
+ renderBlockPlan(block, context) {
1380
+ const entries = block.entries || [];
1381
+ if (entries.length === 0) return null;
1382
+
1383
+ const priorityColors = {
1384
+ high: '#ef4444',
1385
+ medium: '#f59e0b',
1386
+ low: '#6b7280'
1387
+ };
1388
+ const statusIcons = {
1389
+ pending: '○',
1390
+ in_progress: '◐',
1391
+ completed: '●'
1392
+ };
1393
+
1394
+ const div = document.createElement('div');
1395
+ div.className = 'block-plan';
1396
+ div.innerHTML = `
1397
+ <details class="folded-tool folded-tool-info">
1398
+ <summary class="folded-tool-bar">
1399
+ <span class="folded-tool-icon"><svg viewBox="0 0 20 20" fill="currentColor"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/></svg></span>
1400
+ <span class="folded-tool-name">Plan</span>
1401
+ <span class="folded-tool-desc">${entries.length} tasks</span>
1402
+ </summary>
1403
+ <div class="folded-tool-body">
1404
+ <div style="display:flex;flex-direction:column;gap:0.375rem">
1405
+ ${entries.map(e => `
1406
+ <div style="display:flex;align-items:center;gap:0.5rem;font-size:0.8rem">
1407
+ <span style="color:${priorityColors[e.priority] || priorityColors.low}">${statusIcons[e.status] || statusIcons.pending}</span>
1408
+ <span style="${e.status === 'completed' ? 'text-decoration:line-through;opacity:0.6' : ''}">${this.escapeHtml(e.content || '')}</span>
1409
+ </div>
1410
+ `).join('')}
1411
+ </div>
1412
+ </div>
1413
+ </details>
1414
+ `;
1415
+ return div;
1416
+ }
1417
+
1323
1418
  /**
1324
1419
  * Render generic block with formatted key-value pairs
1325
1420
  */
@@ -317,9 +317,23 @@
317
317
  }
318
318
 
319
319
  function splitSentences(text) {
320
+ if (!text) return [text];
320
321
  var raw = text.match(/[^.!?]+[.!?]+[\s]?|[^.!?]+$/g);
321
322
  if (!raw) return [text];
322
- return raw.map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
323
+ var sentences = raw.map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
324
+ var result = [];
325
+ for (var i = 0; i < sentences.length; i++) {
326
+ var s = sentences[i];
327
+ if (result.length > 0) {
328
+ var prev = result[result.length - 1];
329
+ if (s.match(/^(\d+[\.\)]|\d+\s)/) || prev.match(/\d+[\.\)]$/)) {
330
+ result[result.length - 1] = prev + ' ' + s;
331
+ continue;
332
+ }
333
+ }
334
+ result.push(s);
335
+ }
336
+ return result;
323
337
  }
324
338
 
325
339
  var audioChunkQueue = [];
@@ -382,17 +396,9 @@
382
396
  return;
383
397
  }
384
398
 
385
- var sentences = splitSentences(text);
399
+ var sentences = [text];
386
400
  var cachedSentences = [];
387
- var uncachedText = [];
388
- for (var i = 0; i < sentences.length; i++) {
389
- var blob = getCachedTTSBlob(sentences[i]);
390
- if (blob) {
391
- cachedSentences.push({ idx: i, blob: blob });
392
- } else {
393
- uncachedText.push(sentences[i]);
394
- }
395
- }
401
+ var uncachedText = [text];
396
402
 
397
403
  if (cachedSentences.length === sentences.length) {
398
404
  ttsConsecutiveFailures = 0;
@@ -530,16 +536,32 @@
530
536
  if (!container) return;
531
537
  var emptyMsg = container.querySelector('.voice-empty');
532
538
  if (emptyMsg) emptyMsg.remove();
539
+ var lastChild = container.lastElementChild;
540
+ if (!isUser && lastChild && lastChild.classList.contains('voice-block') && !lastChild.classList.contains('voice-block-user')) {
541
+ var contentSpan = lastChild.querySelector('.voice-block-content');
542
+ if (contentSpan) {
543
+ contentSpan.textContent += '\n' + stripHtml(text);
544
+ lastChild._fullText = (lastChild._fullText || contentSpan.textContent) + '\n' + text;
545
+ scrollVoiceToBottom();
546
+ return lastChild;
547
+ }
548
+ }
533
549
  var div = document.createElement('div');
534
550
  div.className = 'voice-block' + (isUser ? ' voice-block-user' : '');
535
- div.textContent = isUser ? text : stripHtml(text);
536
- if (!isUser) {
551
+ if (isUser) {
552
+ div.textContent = text;
553
+ } else {
554
+ var contentSpan = document.createElement('span');
555
+ contentSpan.className = 'voice-block-content';
556
+ contentSpan.textContent = stripHtml(text);
557
+ div.appendChild(contentSpan);
558
+ div._fullText = text;
537
559
  var rereadBtn = document.createElement('button');
538
560
  rereadBtn.className = 'voice-reread-btn';
539
561
  rereadBtn.title = 'Re-read aloud';
540
562
  rereadBtn.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>';
541
563
  rereadBtn.addEventListener('click', function() {
542
- speak(text);
564
+ speak(div._fullText || contentSpan.textContent);
543
565
  });
544
566
  div.appendChild(rereadBtn);
545
567
  }