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 +104 -1
- package/package.json +1 -1
- package/server.js +14 -106
- package/static/index.html +11 -3
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
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ===== */
|