agentgui 1.0.514 → 1.0.516

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
@@ -91,6 +91,30 @@ All routes are prefixed with `BASE_URL` (default `/gm`).
91
91
  - `POST /api/tools/update` - Batch update all tools with available updates
92
92
  - `POST /api/tools/refresh-all` - Refresh all tool statuses from package manager
93
93
 
94
+ ## Tool Update System
95
+
96
+ Tool updates are managed through a complete pipeline:
97
+
98
+ **Update Flow:**
99
+ 1. Frontend (`static/js/tools-manager.js`) initiates POST to `/api/tools/{id}/update`
100
+ 2. Server (`server.js` lines 1904-1961 for individual, 1973-2003 for batch) spawns bunx process
101
+ 3. Tool manager (`lib/tool-manager.js` lines 400-432) executes `bunx <package>` and detects new version
102
+ 4. Version is saved to database: `queries.updateToolStatus(toolId, { version, status: 'installed' })`
103
+ 5. WebSocket broadcasts `tool_update_complete` with version and status data
104
+ 6. Frontend updates UI and removes tool from `operationInProgress` set
105
+
106
+ **Critical Detail:** When updating tools in batch (`/api/tools/update`), the version parameter MUST be included in the database update call (line 1986 in server.js). This ensures database persistence across page reloads.
107
+
108
+ **Version Detection Sources** (`lib/tool-manager.js` lines 26-87):
109
+ - Claude Code: `~/.claude/plugins/{pluginId}/plugin.json`
110
+ - OpenCode: `~/.config/opencode/agents/{pluginId}/plugin.json`
111
+ - Gemini CLI: `~/.gemini/extensions/{pluginId}/plugin.json`
112
+ - Kilo: `~/.config/kilo/agents/{pluginId}/plugin.json`
113
+
114
+ **Database Schema** (`database.js` lines 168-199):
115
+ - Table: `tool_installations` (toolId, version, status, installed_at, error_message)
116
+ - Table: `tool_install_history` (action, status, error_message for audit trail)
117
+
94
118
  ## Tool Detection System
95
119
 
96
120
  The system auto-detects installed AI coding tools via `bunx` package resolution:
@@ -99,7 +123,13 @@ The system auto-detects installed AI coding tools via `bunx` package resolution:
99
123
  - **Kilo**: `@kilocode/cli` package (id: gm-kilo)
100
124
  - **Claude Code**: `@anthropic-ai/claude-code` package (id: gm-cc)
101
125
 
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.
126
+ Tool configuration in `lib/tool-manager.js` TOOLS array includes id, name, pkg, and pluginId. Each tool has a different plugin folder name than its npm package name:
127
+ - Claude Code: pkg='@anthropic-ai/claude-code', pluginId='gm' (stored at ~/.claude/plugins/gm/)
128
+ - Gemini CLI: pkg='@google/gemini-cli', pluginId='gm' (stored at ~/.gemini/extensions/gm/)
129
+ - Kilo: pkg='@kilocode/cli', pluginId='@kilocode/cli' (stored at ~/.config/kilo/agents/@kilocode/cli/)
130
+ - OpenCode: pkg='opencode-ai', pluginId='opencode-ai' (stored at ~/.config/opencode/agents/opencode-ai/)
131
+
132
+ Detection happens by spawning `bunx <package> --version` to check if tools are installed. Version detection uses pluginId to find the correct plugin.json file. 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.
103
133
 
104
134
  ### Tool Installation and Update UI Flow
105
135
 
@@ -162,3 +192,76 @@ Speech models (~470MB total) are downloaded automatically on server startup. No
162
192
 
163
193
  ### Cache Location
164
194
  Models are stored at `~/.gmgui/models/` (whisper in `onnx-community/whisper-base/`, TTS in `tts/`).
195
+
196
+ ## Tool Update Process Fix
197
+
198
+ ### Issue
199
+ Tool update/install operations would complete successfully but the version display in the UI would not update to reflect the new version.
200
+
201
+ ### Root Cause
202
+ The WebSocket broadcast event for tool update/install completion was missing the `version` field. The server was sending only the `freshStatus` object (which contains `installedVersion`), but not including the extracted `version` field from the tool-manager result.
203
+
204
+ Frontend expected: `data.data.version`
205
+ Backend was sending: only `data.data.installedVersion`
206
+
207
+ ### Solution
208
+ Updated WebSocket broadcasts in `server.js`:
209
+ - Line 1883: Install endpoint now includes `version` in broadcast data
210
+ - Line 1942: Update endpoint now includes `version` in broadcast data
211
+ - Line 1987: Legacy install endpoint now saves `version` to database
212
+
213
+ The broadcasts now include both the immediately-detected `version` field and the comprehensive `freshStatus` object, ensuring the frontend has complete information to update the UI correctly.
214
+
215
+ ### Testing
216
+ After update/install completes:
217
+ 1. WebSocket event `tool_update_complete` or `tool_install_complete` is broadcast
218
+ 2. Frontend receives complete data with `version`, `installedVersion`, `isUpToDate`, etc.
219
+ 3. UI version display updates to show new version
220
+ 4. Status reverts to "Installed" or "Up-to-date" accordingly
221
+
222
+ ## Tool Update Testing & Diagnostics
223
+
224
+ A comprehensive diagnostic page is available at `http://localhost:3000/gm/tool-update-test.html` (`static/tool-update-test.html`) with 7 interactive test sections:
225
+
226
+ 1. **API Connection Test** - Verifies server HTTP connectivity
227
+ 2. **Get Tools Status** - Lists all tools with their current status, versions, and update availability
228
+ 3. **WebSocket Connection Test** - Tests real-time event streaming (ping/pong)
229
+ 4. **Single Tool Update Test** - Triggers update for a specific tool and monitors completion
230
+ 5. **Event Stream Monitoring** - Watches all WebSocket events in real-time
231
+ 6. **Database Status** - Checks database accessibility and tool persistence
232
+ 7. **System Info** - Displays environment and configuration details
233
+
234
+ ### Batch Update Fix (Critical)
235
+
236
+ **Issue:** When updating all tools via `/api/tools/update` endpoint, tool versions were not persisted to the database because the `version` parameter was missing from the `updateToolStatus` call.
237
+
238
+ **Location:** `server.js` line 1986 in the batch update handler (`/api/tools/update`)
239
+
240
+ **Fix Applied:**
241
+ ```javascript
242
+ // BEFORE (missing version):
243
+ queries.updateToolStatus(toolId, { status: 'installed', installed_at: Date.now() });
244
+
245
+ // AFTER (version preserved):
246
+ const version = result.version || null;
247
+ queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
248
+ ```
249
+
250
+ **Impact:** Ensures tool versions are correctly saved after batch updates, enabling the UI to display accurate version information and update status across page reloads.
251
+
252
+ ### Testing Tool Updates
253
+
254
+ **Manual Steps:**
255
+ 1. Open `http://localhost:3000/gm/tool-update-test.html`
256
+ 2. Click "Get Tools List" and note current versions
257
+ 3. Click "Start Update" for a tool (e.g., gm-cc)
258
+ 4. Monitor WebSocket events - you should see `tool_update_progress` and `tool_update_complete`
259
+ 5. Click "Check Status" to verify version was saved to database
260
+ 6. Reload the page - versions should persist
261
+
262
+ **Expected Outcomes:**
263
+ - Individual tool update: version saved ✓
264
+ - Batch tool update: version saved for all tools ✓
265
+ - Database persists across page reload ✓
266
+ - Frontend shows "Up-to-date" or "Update available" ✓
267
+ - Tool install history records the action ✓
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.514",
3
+ "version": "1.0.516",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -1880,7 +1880,7 @@ const server = http.createServer(async (req, res) => {
1880
1880
  queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
1881
1881
  const freshStatus = await toolManager.checkToolStatusAsync(toolId);
1882
1882
  console.log(`[TOOLS-API] Fresh status after install for ${toolId}:`, JSON.stringify(freshStatus));
1883
- broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, ...freshStatus } });
1883
+ broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, version, ...freshStatus } });
1884
1884
  queries.addToolInstallHistory(toolId, 'install', 'success', null);
1885
1885
  } else {
1886
1886
  console.error(`[TOOLS-API] Install failed for ${toolId}:`, result.error);
@@ -1939,7 +1939,7 @@ const server = http.createServer(async (req, res) => {
1939
1939
  queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
1940
1940
  const freshStatus = await toolManager.checkToolStatusAsync(toolId);
1941
1941
  console.log(`[TOOLS-API] Fresh status after update for ${toolId}:`, JSON.stringify(freshStatus));
1942
- broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, ...freshStatus } });
1942
+ broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, version, ...freshStatus } });
1943
1943
  queries.addToolInstallHistory(toolId, 'update', 'success', null);
1944
1944
  } else {
1945
1945
  console.error(`[TOOLS-API] Update failed for ${toolId}:`, result.error);
@@ -1970,133 +1970,43 @@ const server = http.createServer(async (req, res) => {
1970
1970
  return;
1971
1971
  }
1972
1972
 
1973
-
1974
- // Handle POST /api/tools/{toolId}/install - individual tool install
1975
- const installMatch = pathOnly.match(/^\/api\/tools\/([^\/]+)\/install$/);
1976
- if (installMatch && req.method === 'POST') {
1977
- const toolId = installMatch[1];
1978
- sendJSON(req, res, 200, { installing: true, toolId });
1979
- setImmediate(async () => {
1980
- try {
1981
- const result = await toolManager.install(toolId, (msg) => {
1982
- if (wsOptimizer && wsOptimizer.broadcast) {
1983
- wsOptimizer.broadcast({ type: 'tool_install_progress', toolId, data: msg });
1984
- }
1985
- });
1986
- if (result.success) {
1987
- queries.updateToolStatus(toolId, { status: 'installed', installed_at: Date.now() });
1988
- queries.addToolInstallHistory(toolId, 'install', 'success', null);
1989
- const freshStatus = await toolManager.checkToolStatusAsync(toolId);
1990
- if (wsOptimizer && wsOptimizer.broadcast) {
1991
- wsOptimizer.broadcast({ type: 'tool_install_complete', toolId, data: { ...result, ...freshStatus } });
1992
- }
1993
- } else {
1994
- queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
1995
- queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
1996
- if (wsOptimizer && wsOptimizer.broadcast) {
1997
- wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: result });
1998
- }
1999
- }
2000
- } catch (err) {
2001
- queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
2002
- queries.addToolInstallHistory(toolId, 'install', 'failed', err.message);
2003
- if (wsOptimizer && wsOptimizer.broadcast) {
2004
- wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: { success: false, error: err.message } });
2005
- }
2006
- }
2007
- });
2008
- return;
2009
- }
2010
-
2011
- // Handle POST /api/tools/{toolId}/update - individual tool update
2012
- const updateMatch = pathOnly.match(/^\/api\/tools\/([^\/]+)\/update$/);
2013
- if (updateMatch && req.method === 'POST') {
2014
- const toolId = updateMatch[1];
2015
- sendJSON(req, res, 200, { updating: true, toolId });
2016
- setImmediate(async () => {
2017
- try {
2018
- const result = await toolManager.update(toolId, (msg) => {
2019
- if (wsOptimizer && wsOptimizer.broadcast) {
2020
- wsOptimizer.broadcast({ type: 'tool_update_progress', toolId, data: msg });
2021
- }
2022
- });
2023
- if (result.success) {
2024
- queries.updateToolStatus(toolId, { status: 'installed', installed_at: Date.now() });
2025
- queries.addToolInstallHistory(toolId, 'update', 'success', null);
2026
- const freshStatus = await toolManager.checkToolStatusAsync(toolId);
2027
- if (wsOptimizer && wsOptimizer.broadcast) {
2028
- wsOptimizer.broadcast({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
2029
- }
2030
- } else {
2031
- queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
2032
- queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
2033
- if (wsOptimizer && wsOptimizer.broadcast) {
2034
- wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: result });
2035
- }
2036
- }
2037
- } catch (err) {
2038
- queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
2039
- queries.addToolInstallHistory(toolId, 'update', 'failed', err.message);
2040
- if (wsOptimizer && wsOptimizer.broadcast) {
2041
- wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: { success: false, error: err.message } });
2042
- }
2043
- }
2044
- });
2045
- return;
2046
- }
2047
-
2048
1973
  if (pathOnly === '/api/tools/update' && req.method === 'POST') {
2049
1974
  sendJSON(req, res, 200, { updating: true, toolCount: 4 });
2050
- if (wsOptimizer && wsOptimizer.broadcast) {
2051
- wsOptimizer.broadcast({ type: 'tools_update_started', tools: ['gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'] });
2052
- }
1975
+ broadcastSync({ type: 'tools_update_started', tools: ['gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'] });
2053
1976
  setImmediate(async () => {
2054
1977
  const toolIds = ['gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
2055
1978
  const results = {};
2056
1979
  for (const toolId of toolIds) {
2057
1980
  try {
2058
1981
  const result = await toolManager.update(toolId, (msg) => {
2059
- if (wsOptimizer && wsOptimizer.broadcast) {
2060
- wsOptimizer.broadcast({ type: 'tool_update_progress', toolId, data: msg });
2061
- }
1982
+ broadcastSync({ type: 'tool_update_progress', toolId, data: msg });
2062
1983
  });
2063
1984
  results[toolId] = result;
2064
1985
  if (result.success) {
2065
- queries.updateToolStatus(toolId, { status: 'installed', installed_at: Date.now() });
1986
+ const version = result.version || null;
1987
+ queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
2066
1988
  queries.addToolInstallHistory(toolId, 'update', 'success', null);
2067
1989
  const freshStatus = await toolManager.checkToolStatusAsync(toolId);
2068
- if (wsOptimizer && wsOptimizer.broadcast) {
2069
- wsOptimizer.broadcast({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
2070
- }
1990
+ broadcastSync({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
2071
1991
  } else {
2072
1992
  queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
2073
1993
  queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
2074
- if (wsOptimizer && wsOptimizer.broadcast) {
2075
- wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: result });
2076
- }
1994
+ broadcastSync({ type: 'tool_update_failed', toolId, data: result });
2077
1995
  }
2078
1996
  } catch (err) {
2079
- const error = err.message || 'Unknown error';
2080
- results[toolId] = { success: false, error };
2081
- queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
2082
- queries.addToolInstallHistory(toolId, 'update', 'failed', error);
2083
- if (wsOptimizer && wsOptimizer.broadcast) {
2084
- wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: { error } });
2085
- }
1997
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
1998
+ queries.addToolInstallHistory(toolId, 'update', 'failed', err.message);
1999
+ broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: err.message } });
2086
2000
  }
2087
2001
  }
2088
- if (wsOptimizer && wsOptimizer.broadcast) {
2089
- wsOptimizer.broadcast({ type: 'tools_update_complete', data: results });
2090
- }
2002
+ broadcastSync({ type: 'tools_update_complete', data: results });
2091
2003
  });
2092
2004
  return;
2093
2005
  }
2094
2006
 
2095
2007
  if (pathOnly === '/api/tools/refresh-all' && req.method === 'POST') {
2096
2008
  sendJSON(req, res, 200, { refreshing: true, toolCount: 4 });
2097
- if (wsOptimizer && wsOptimizer.broadcast) {
2098
- wsOptimizer.broadcast({ type: 'tools_refresh_started' });
2099
- }
2009
+ broadcastSync({ type: 'tools_refresh_started' });
2100
2010
  setImmediate(async () => {
2101
2011
  const tools = toolManager.getAllTools();
2102
2012
  for (const tool of tools) {
@@ -2112,9 +2022,7 @@ const server = http.createServer(async (req, res) => {
2112
2022
  }
2113
2023
  }
2114
2024
  }
2115
- if (wsOptimizer && wsOptimizer.broadcast) {
2116
- wsOptimizer.broadcast({ type: 'tools_refresh_complete', data: tools });
2117
- }
2025
+ broadcastSync({ type: 'tools_refresh_complete', data: tools });
2118
2026
  });
2119
2027
  return;
2120
2028
  }
package/static/index.html CHANGED
@@ -856,6 +856,14 @@
856
856
  display: none;
857
857
  }
858
858
 
859
+ .cli-selector {
860
+ display: inline-block;
861
+ }
862
+
863
+ .sub-agent-selector {
864
+ display: inline-block;
865
+ }
866
+
859
867
  .message-textarea {
860
868
  flex: 1;
861
869
  padding: 0.625rem 0.875rem;
@@ -1087,9 +1095,9 @@
1087
1095
  .message { max-width: 95%; }
1088
1096
  .messages-wrapper { padding: 0.375rem 0.5rem; }
1089
1097
  .input-section { padding: 0.5rem; padding-bottom: calc(0.5rem + env(safe-area-inset-bottom)); }
1090
- .cli-selector { display: none; }
1091
- .sub-agent-selector { display: none; }
1092
- .model-selector { display: none; }
1098
+ .cli-selector { display: none !important; }
1099
+ .sub-agent-selector { display: none !important; }
1100
+ .model-selector { display: none !important; }
1093
1101
  }
1094
1102
 
1095
1103
  /* ===== SCROLLBAR STYLING ===== */