agentgui 1.0.502 → 1.0.504

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 CHANGED
@@ -84,6 +84,12 @@ All routes are prefixed with `BASE_URL` (default `/gm`).
84
84
  - `GET /api/speech-status` - Speech model loading status
85
85
  - `POST /api/folders` - Create folder
86
86
  - `GET /api/tools` - List detected tools with installation status (via WebSocket tools.list handler)
87
+ - `GET /api/tools/:id/status` - Get tool installation status (version, installed_at, error_message)
88
+ - `POST /api/tools/:id/install` - Start tool installation (returns `{ success: true }` with background async install)
89
+ - `POST /api/tools/:id/update` - Start tool update (body: targetVersion)
90
+ - `GET /api/tools/:id/history` - Get tool install/update history (query: limit, offset)
91
+ - `POST /api/tools/update` - Batch update all tools with available updates
92
+ - `POST /api/tools/refresh-all` - Refresh all tool statuses from package manager
87
93
 
88
94
  ## Tool Detection System
89
95
 
@@ -95,6 +101,29 @@ The system auto-detects installed AI coding tools via `bunx` package resolution:
95
101
 
96
102
  Tool package names are configured in `lib/tool-manager.js` TOOLS array (lines 6-11). Detection happens by spawning `bunx <package> --version` to check if tools are installed. Response from `/api/tools` includes: id, name, pkg, installed, status (one of: installed|needs_update|not_installed), isUpToDate, upgradeNeeded, hasUpdate. Frontend displays tools in UI and updates based on installation status.
97
103
 
104
+ ### Tool Installation and Update UI Flow
105
+
106
+ When user clicks Install/Update button on a tool:
107
+
108
+ 1. **Frontend** (`static/js/tools-manager.js`):
109
+ - Immediately updates tool status to 'installing'/'updating' and re-renders UI
110
+ - Sends POST request to `/api/tools/{id}/install` or `/api/tools/{id}/update`
111
+ - Adds toolId to `operationInProgress` to prevent duplicate requests
112
+ - Button becomes disabled showing progress indicator while install runs
113
+
114
+ 2. **Backend** (`server.js` lines 1819-1851):
115
+ - Receives POST request, updates database status to 'installing'/'updating'
116
+ - Sends immediate response `{ success: true }`
117
+ - Asynchronously calls `toolManager.install/update()` in background
118
+ - Upon completion, broadcasts WebSocket event `tool_install_complete` or `tool_install_failed`
119
+
120
+ 3. **Frontend WebSocket Handler** (`static/js/tools-manager.js` lines 138-151):
121
+ - Listens for `tool_install_complete` or `tool_install_failed` events
122
+ - Updates tool status and re-renders final state
123
+ - Removes toolId from `operationInProgress`, enabling button again
124
+
125
+ The UI shows progress in three phases: immediate "Installing" status, progress bar animation during install, and final "Installed"/"Failed" status when complete.
126
+
98
127
  ## WebSocket Protocol
99
128
 
100
129
  Endpoint: `BASE_URL + /sync`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.502",
3
+ "version": "1.0.504",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -1890,7 +1890,7 @@ class AgentGUIClient {
1890
1890
  if (this.ui.cliSelector) {
1891
1891
  if (displayAgents.length > 0) {
1892
1892
  this.ui.cliSelector.innerHTML = displayAgents
1893
- .map(a => `<option value="${a.id}">${a.name}</option>`)
1893
+ .map(a => `<option value="${a.id}">${a.name.split(/[\s\-]+/)[0]}</option>`)
1894
1894
  .join('');
1895
1895
  this.ui.cliSelector.style.display = 'inline-block';
1896
1896
  } else {
@@ -1923,7 +1923,7 @@ class AgentGUIClient {
1923
1923
  const { subAgents } = await window.wsClient.rpc('agent.subagents', { id: cliAgentId });
1924
1924
  if (subAgents && subAgents.length > 0 && this.ui.agentSelector) {
1925
1925
  this.ui.agentSelector.innerHTML = subAgents
1926
- .map(a => `<option value="${a.id}">${a.name}</option>`)
1926
+ .map(a => `<option value="${a.id}">${a.name.split(/[\s\-]+/)[0]}</option>`)
1927
1927
  .join('');
1928
1928
  this.ui.agentSelector.style.display = 'inline-block';
1929
1929
  this.loadModelsForAgent(cliAgentId);
@@ -508,6 +508,9 @@
508
508
  var streamingSupported = true;
509
509
  var streamingFailedAt = 0;
510
510
 
511
+ var pendingVoiceUpdates = [];
512
+ var MAX_PENDING_UPDATES = 100;
513
+
511
514
  function optimizePromptForSpeech(text) {
512
515
  var optimizationInstructions = ' [Optimize for speech: Keep it short. Use simple words. Use short sentences. Focus on clarity.]';
513
516
  return text + optimizationInstructions;
@@ -813,22 +816,29 @@
813
816
  if (data.type === 'sync_connected') {
814
817
  sendVoiceToServer();
815
818
  }
819
+ if (data.type === 'streaming_progress' || data.type === 'message_created' || data.type === 'streaming_start') {
820
+ if (data.conversationId && data.conversationId !== currentConversationId) return;
821
+ if (!voiceActive) {
822
+ pendingVoiceUpdates.push(data);
823
+ if (pendingVoiceUpdates.length > MAX_PENDING_UPDATES) {
824
+ pendingVoiceUpdates.shift();
825
+ }
826
+ return;
827
+ }
828
+ }
816
829
  if (!voiceActive) return;
817
830
  if (data.type === 'streaming_progress' && data.block) {
818
- if (data.conversationId && data.conversationId !== currentConversationId) return;
819
831
  if (data.seq !== undefined && renderedSeqs.has(data.seq)) return;
820
832
  if (data.seq !== undefined) renderedSeqs.add(data.seq);
821
833
  handleVoiceBlock(data.block, true, data.blockRole);
822
834
  }
823
835
  if (data.type === 'message_created' && data.message) {
824
- if (data.conversationId && data.conversationId !== currentConversationId) return;
825
836
  var message = data.message;
826
837
  if (message.role === 'user' && message.content) {
827
838
  handleVoiceBlock({ type: 'text', text: message.content }, true, 'user');
828
839
  }
829
840
  }
830
841
  if (data.type === 'streaming_start') {
831
- if (data.conversationId && data.conversationId !== currentConversationId) return;
832
842
  spokenChunks = new Set();
833
843
  renderedSeqs = new Set();
834
844
  _voiceBreakNext = false;
@@ -838,13 +848,16 @@
838
848
  var newConversationId = e.detail.conversationId;
839
849
  if (currentConversationId && currentConversationId !== newConversationId) {
840
850
  unsubscribeFromConversation();
851
+ pendingVoiceUpdates = [];
841
852
  }
842
853
  currentConversationId = newConversationId;
843
854
  stopSpeaking();
844
855
  spokenChunks = new Set();
845
856
  renderedSeqs = new Set();
846
857
  if (voiceActive) {
858
+ subscribeToConversation(currentConversationId);
847
859
  loadVoiceBlocks(currentConversationId);
860
+ processPendingUpdates();
848
861
  }
849
862
  });
850
863
  }
@@ -941,7 +954,9 @@
941
954
  function activate() {
942
955
  voiceActive = true;
943
956
  if (currentConversationId) {
957
+ subscribeToConversation(currentConversationId);
944
958
  loadVoiceBlocks(currentConversationId);
959
+ processPendingUpdates();
945
960
  } else {
946
961
  var container = document.getElementById('voiceMessages');
947
962
  if (container && !container.hasChildNodes()) {
@@ -950,10 +965,35 @@
950
965
  }
951
966
  }
952
967
 
968
+ function processPendingUpdates() {
969
+ if (!voiceActive) return;
970
+ var updates = pendingVoiceUpdates.splice(0, pendingVoiceUpdates.length);
971
+ for (var i = 0; i < updates.length; i++) {
972
+ var data = updates[i];
973
+ if (data.type === 'streaming_progress' && data.block) {
974
+ if (data.seq !== undefined && renderedSeqs.has(data.seq)) continue;
975
+ if (data.seq !== undefined) renderedSeqs.add(data.seq);
976
+ handleVoiceBlock(data.block, true, data.blockRole);
977
+ }
978
+ if (data.type === 'message_created' && data.message) {
979
+ var message = data.message;
980
+ if (message.role === 'user' && message.content) {
981
+ handleVoiceBlock({ type: 'text', text: message.content }, true, 'user');
982
+ }
983
+ }
984
+ if (data.type === 'streaming_start') {
985
+ spokenChunks = new Set();
986
+ renderedSeqs = new Set();
987
+ _voiceBreakNext = false;
988
+ }
989
+ }
990
+ }
991
+
953
992
  function deactivate() {
954
993
  voiceActive = false;
955
994
  stopSpeaking();
956
995
  unsubscribeFromConversation();
996
+ pendingVoiceUpdates = [];
957
997
  }
958
998
 
959
999
  function escapeHtml(text) {