agentgui 1.0.760 → 1.0.761

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.
@@ -0,0 +1,185 @@
1
+ import * as toolInstallMachine from './tool-install-machine.js';
2
+
3
+ export function register(deps) {
4
+ const { sendJSON, parseBody, queries, broadcastSync, logError, toolManager } = deps;
5
+
6
+ function mapTool(t) {
7
+ return {
8
+ id: t.id, name: t.name, pkg: t.pkg,
9
+ category: t.category || 'plugin',
10
+ installed: t.installed || false,
11
+ status: t.installed ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
12
+ isUpToDate: t.isUpToDate || false,
13
+ upgradeNeeded: t.upgradeNeeded || false,
14
+ hasUpdate: (t.upgradeNeeded && t.installed) || false,
15
+ installedVersion: t.installedVersion || null,
16
+ publishedVersion: t.publishedVersion || null,
17
+ machineState: toolInstallMachine.getState(t.id),
18
+ };
19
+ }
20
+
21
+ const routes = {};
22
+
23
+ routes['GET /api/tools'] = async (req, res) => {
24
+ try {
25
+ const tools = await Promise.race([
26
+ toolManager.getAllToolsAsync(true),
27
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000))
28
+ ]);
29
+ sendJSON(req, res, 200, { tools: tools.map(mapTool) });
30
+ } catch (err) {
31
+ sendJSON(req, res, 200, { tools: toolManager.getAllToolsSync().map(mapTool) });
32
+ }
33
+ };
34
+
35
+ routes['POST /api/tools/update'] = async (req, res) => {
36
+ const allToolIds = ['cli-claude', 'cli-opencode', 'cli-gemini', 'cli-kilo', 'cli-codex', 'gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
37
+ sendJSON(req, res, 200, { updating: true, toolCount: allToolIds.length });
38
+ broadcastSync({ type: 'tools_update_started', tools: allToolIds });
39
+ setImmediate(async () => {
40
+ const results = {};
41
+ for (const toolId of allToolIds) {
42
+ try {
43
+ const result = await toolManager.update(toolId, (msg) => broadcastSync({ type: 'tool_update_progress', toolId, data: msg }));
44
+ results[toolId] = result;
45
+ if (result.success) {
46
+ const version = result.version || null;
47
+ queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
48
+ queries.addToolInstallHistory(toolId, 'update', 'success', null);
49
+ const freshStatus = await toolManager.checkToolStatusAsync(toolId);
50
+ broadcastSync({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
51
+ } else {
52
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
53
+ queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
54
+ broadcastSync({ type: 'tool_update_failed', toolId, data: result });
55
+ }
56
+ } catch (err) {
57
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
58
+ queries.addToolInstallHistory(toolId, 'update', 'failed', err.message);
59
+ broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: err.message } });
60
+ }
61
+ }
62
+ broadcastSync({ type: 'tools_update_complete', data: results });
63
+ });
64
+ };
65
+
66
+ routes['POST /api/tools/refresh-all'] = async (req, res) => {
67
+ sendJSON(req, res, 200, { refreshing: true, toolCount: 4 });
68
+ broadcastSync({ type: 'tools_refresh_started' });
69
+ setImmediate(async () => {
70
+ const tools = toolManager.getAllTools();
71
+ for (const tool of tools) {
72
+ queries.updateToolStatus(tool.id, { status: tool.installed ? 'installed' : 'not_installed', version: tool.installedVersion, last_check_at: Date.now() });
73
+ if (tool.installed) {
74
+ const status = await toolManager.checkToolStatusAsync(tool.id);
75
+ if (status?.upgradeNeeded) queries.updateToolStatus(tool.id, { update_available: 1, latest_version: status.publishedVersion });
76
+ }
77
+ }
78
+ broadcastSync({ type: 'tools_refresh_complete', data: tools });
79
+ });
80
+ };
81
+
82
+ routes['_match'] = (method, pathOnly) => {
83
+ const key = `${method} ${pathOnly}`;
84
+ if (routes[key]) return routes[key];
85
+ let m;
86
+ if ((m = pathOnly.match(/^\/api\/tools\/([^/]+)\/status$/))) return (req, res) => handleToolStatus(req, res, m[1]);
87
+ if (method === 'POST' && (m = pathOnly.match(/^\/api\/tools\/([^/]+)\/install$/))) return (req, res) => handleToolInstall(req, res, m[1]);
88
+ if (method === 'POST' && (m = pathOnly.match(/^\/api\/tools\/([^/]+)\/update$/))) return (req, res) => handleToolUpdate(req, res, m[1]);
89
+ if (method === 'GET' && (m = pathOnly.match(/^\/api\/tools\/([^/]+)\/history$/))) return (req, res) => handleToolHistory(req, res, m[1]);
90
+ return null;
91
+ };
92
+
93
+ async function handleToolStatus(req, res, toolId) {
94
+ const dbStatus = queries.getToolStatus(toolId);
95
+ const tmStatus = toolManager.checkToolStatus(toolId);
96
+ if (!tmStatus && !dbStatus) { sendJSON(req, res, 404, { error: 'Tool not found' }); return; }
97
+ const status = {
98
+ toolId, installed: tmStatus?.installed || (dbStatus?.status === 'installed'),
99
+ isUpToDate: tmStatus?.isUpToDate || false, upgradeNeeded: tmStatus?.upgradeNeeded || false,
100
+ status: dbStatus?.status || (tmStatus?.installed ? 'installed' : 'not_installed'),
101
+ installedVersion: dbStatus?.version || tmStatus?.installedVersion || null,
102
+ timestamp: Date.now(), error_message: dbStatus?.error_message || null
103
+ };
104
+ if (status.installed) { const updates = await toolManager.checkForUpdates(toolId); status.hasUpdate = updates.needsUpdate || false; }
105
+ sendJSON(req, res, 200, status);
106
+ }
107
+
108
+ async function handleToolInstall(req, res, toolId) {
109
+ const tool = toolManager.getToolConfig(toolId);
110
+ if (!tool) { sendJSON(req, res, 404, { error: 'Tool not found' }); return; }
111
+ const existing = queries.getToolStatus(toolId);
112
+ if (!existing) queries.insertToolInstallation(toolId, { status: 'not_installed' });
113
+ queries.updateToolStatus(toolId, { status: 'installing' });
114
+ sendJSON(req, res, 200, { success: true, installing: true, estimatedTime: 60000 });
115
+ let done = false;
116
+ const timeout = setTimeout(() => {
117
+ if (!done) { done = true; queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Install timeout after 6 minutes' }); broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error: 'Install timeout after 6 minutes' } }); queries.addToolInstallHistory(toolId, 'install', 'failed', 'Install timeout after 6 minutes'); }
118
+ }, 360000);
119
+ toolManager.install(toolId, (msg) => broadcastSync({ type: 'tool_install_progress', toolId, data: msg })).then(async (result) => {
120
+ clearTimeout(timeout); if (done) return; done = true;
121
+ if (result.success) {
122
+ const version = result.version || null;
123
+ queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
124
+ const freshStatus = await toolManager.checkToolStatusAsync(toolId);
125
+ broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, version, ...freshStatus } });
126
+ queries.addToolInstallHistory(toolId, 'install', 'success', null);
127
+ } else {
128
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
129
+ broadcastSync({ type: 'tool_install_failed', toolId, data: result });
130
+ queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
131
+ }
132
+ }).catch((err) => {
133
+ clearTimeout(timeout); if (done) return; done = true;
134
+ const error = err?.message || 'Unknown error';
135
+ logError('toolInstall', err, { toolId });
136
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
137
+ broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error } });
138
+ queries.addToolInstallHistory(toolId, 'install', 'failed', error);
139
+ });
140
+ }
141
+
142
+ async function handleToolUpdate(req, res, toolId) {
143
+ const body = await parseBody(req);
144
+ const tool = toolManager.getToolConfig(toolId);
145
+ if (!tool) { sendJSON(req, res, 404, { error: 'Tool not found' }); return; }
146
+ const current = await toolManager.checkToolStatusAsync(toolId);
147
+ if (!current || !current.installed) { sendJSON(req, res, 400, { error: 'Tool not installed' }); return; }
148
+ queries.updateToolStatus(toolId, { status: 'updating' });
149
+ sendJSON(req, res, 200, { success: true, updating: true });
150
+ let done = false;
151
+ const timeout = setTimeout(() => {
152
+ if (!done) { done = true; queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Update timeout after 6 minutes' }); broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: 'Update timeout after 6 minutes' } }); queries.addToolInstallHistory(toolId, 'update', 'failed', 'Update timeout after 6 minutes'); }
153
+ }, 360000);
154
+ toolManager.update(toolId, (msg) => broadcastSync({ type: 'tool_update_progress', toolId, data: msg })).then(async (result) => {
155
+ clearTimeout(timeout); if (done) return; done = true;
156
+ if (result.success) {
157
+ const version = result.version || null;
158
+ queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
159
+ const freshStatus = await toolManager.checkToolStatusAsync(toolId);
160
+ broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, version, ...freshStatus } });
161
+ queries.addToolInstallHistory(toolId, 'update', 'success', null);
162
+ } else {
163
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
164
+ broadcastSync({ type: 'tool_update_failed', toolId, data: result });
165
+ queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
166
+ }
167
+ }).catch((err) => {
168
+ clearTimeout(timeout); if (done) return; done = true;
169
+ const error = err?.message || 'Unknown error';
170
+ logError('toolUpdate', err, { toolId });
171
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
172
+ broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error } });
173
+ queries.addToolInstallHistory(toolId, 'update', 'failed', error);
174
+ });
175
+ }
176
+
177
+ async function handleToolHistory(req, res, toolId) {
178
+ const url = new URL(req.url, 'http://localhost');
179
+ const limit = Math.min(parseInt(url.searchParams.get('limit')) || 20, 100);
180
+ const offset = parseInt(url.searchParams.get('offset')) || 0;
181
+ sendJSON(req, res, 200, { history: queries.getToolInstallHistory(toolId, limit, offset) });
182
+ }
183
+
184
+ return routes;
185
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.760",
3
+ "version": "1.0.761",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
package/server.js CHANGED
@@ -22,6 +22,7 @@ import { initSpeechManager, getSpeech, ensurePocketTtsSetup, voiceCacheManager,
22
22
  import { register as registerSpeechRoutes } from './lib/routes-speech.js';
23
23
  import { register as registerOAuthRoutes } from './lib/routes-oauth.js';
24
24
  import { register as registerUtilRoutes } from './lib/routes-util.js';
25
+ import { register as registerToolRoutes } from './lib/routes-tools.js';
25
26
  import { startCodexOAuth, exchangeCodexOAuthCode, handleCodexOAuthCallback, getCodexOAuthStatus, getCodexOAuthState, CODEX_HOME, CODEX_AUTH_FILE } from './lib/oauth-codex.js';
26
27
  import { WSOptimizer } from './lib/ws-optimizer.js';
27
28
  import { WsRouter } from './lib/ws-protocol.js';
@@ -1278,264 +1279,8 @@ const server = http.createServer(async (req, res) => {
1278
1279
  return;
1279
1280
  }
1280
1281
 
1281
- if (pathOnly === '/api/tools' && req.method === 'GET') {
1282
- console.log('[TOOLS-API] Handling GET /api/tools');
1283
- try {
1284
- // Return immediately with cached data (non-blocking) - skip network version checks
1285
- const tools = await Promise.race([
1286
- toolManager.getAllToolsAsync(true), // skipPublishedVersion=true for fast response
1287
- new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000))
1288
- ]);
1289
- const result = tools.map((t) => ({
1290
- id: t.id,
1291
- name: t.name,
1292
- pkg: t.pkg,
1293
- category: t.category || 'plugin',
1294
- installed: t.installed,
1295
- status: t.installed ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
1296
- isUpToDate: t.isUpToDate,
1297
- upgradeNeeded: t.upgradeNeeded,
1298
- hasUpdate: t.upgradeNeeded && t.installed,
1299
- installedVersion: t.installedVersion,
1300
- publishedVersion: t.publishedVersion,
1301
- machineState: toolInstallMachine.getState(t.id),
1302
- }));
1303
- sendJSON(req, res, 200, { tools: result });
1304
- } catch (err) {
1305
- console.log('[TOOLS-API] Error getting tools, returning cached status:', err.message);
1306
- const tools = toolManager.getAllToolsSync().map((t) => ({
1307
- id: t.id,
1308
- name: t.name,
1309
- pkg: t.pkg,
1310
- category: t.category || 'plugin',
1311
- installed: t.installed || false,
1312
- status: (t.installed) ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
1313
- isUpToDate: t.isUpToDate || false,
1314
- upgradeNeeded: t.upgradeNeeded || false,
1315
- hasUpdate: (t.upgradeNeeded && t.installed) || false,
1316
- installedVersion: t.installedVersion || null,
1317
- publishedVersion: t.publishedVersion || null,
1318
- machineState: toolInstallMachine.getState(t.id),
1319
- }));
1320
- sendJSON(req, res, 200, { tools });
1321
- }
1322
- return;
1323
- }
1324
-
1325
- if (pathOnly.match(/^\/api\/tools\/([^/]+)\/status$/)) {
1326
- const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/status$/)[1];
1327
- const dbStatus = queries.getToolStatus(toolId);
1328
- const tmStatus = toolManager.checkToolStatus(toolId);
1329
- if (!tmStatus && !dbStatus) {
1330
- sendJSON(req, res, 404, { error: 'Tool not found' });
1331
- return;
1332
- }
1333
-
1334
- // Merge database status with tool manager status
1335
- const status = {
1336
- toolId,
1337
- installed: tmStatus?.installed || (dbStatus?.status === 'installed'),
1338
- isUpToDate: tmStatus?.isUpToDate || false,
1339
- upgradeNeeded: tmStatus?.upgradeNeeded || false,
1340
- status: dbStatus?.status || (tmStatus?.installed ? 'installed' : 'not_installed'),
1341
- installedVersion: dbStatus?.version || tmStatus?.installedVersion || null,
1342
- timestamp: Date.now(),
1343
- error_message: dbStatus?.error_message || null
1344
- };
1345
-
1346
- if (status.installed) {
1347
- const updates = await toolManager.checkForUpdates(toolId);
1348
- status.hasUpdate = updates.needsUpdate || false;
1349
- }
1350
- sendJSON(req, res, 200, status);
1351
- return;
1352
- }
1353
-
1354
- if (pathOnly.match(/^\/api\/tools\/([^/]+)\/install$/) && req.method === 'POST') {
1355
- const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/install$/)[1];
1356
- const tool = toolManager.getToolConfig(toolId);
1357
- if (!tool) {
1358
- sendJSON(req, res, 404, { error: 'Tool not found' });
1359
- return;
1360
- }
1361
- const existing = queries.getToolStatus(toolId);
1362
- if (!existing) {
1363
- queries.insertToolInstallation(toolId, { status: 'not_installed' });
1364
- }
1365
- queries.updateToolStatus(toolId, { status: 'installing' });
1366
- sendJSON(req, res, 200, { success: true, installing: true, estimatedTime: 60000 });
1367
-
1368
- let installCompleted = false;
1369
- const installTimeout = setTimeout(() => {
1370
- if (!installCompleted) {
1371
- installCompleted = true;
1372
- queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Install timeout after 6 minutes' });
1373
- broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error: 'Install timeout after 6 minutes' } });
1374
- queries.addToolInstallHistory(toolId, 'install', 'failed', 'Install timeout after 6 minutes');
1375
- }
1376
- }, 360000);
1377
-
1378
- toolManager.install(toolId, (msg) => {
1379
- broadcastSync({ type: 'tool_install_progress', toolId, data: msg });
1380
- }).then(async (result) => {
1381
- clearTimeout(installTimeout);
1382
- if (installCompleted) return;
1383
- installCompleted = true;
1384
- if (result.success) {
1385
- const version = result.version || null;
1386
- console.log(`[TOOLS-API] Install succeeded for ${toolId}, version: ${version}`);
1387
- queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
1388
- const freshStatus = await toolManager.checkToolStatusAsync(toolId);
1389
- console.log(`[TOOLS-API] Fresh status after install for ${toolId}:`, JSON.stringify(freshStatus));
1390
- broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, version, ...freshStatus } });
1391
- queries.addToolInstallHistory(toolId, 'install', 'success', null);
1392
- } else {
1393
- console.error(`[TOOLS-API] Install failed for ${toolId}:`, result.error);
1394
- queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
1395
- broadcastSync({ type: 'tool_install_failed', toolId, data: result });
1396
- queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
1397
- }
1398
- }).catch((err) => {
1399
- clearTimeout(installTimeout);
1400
- if (installCompleted) return;
1401
- installCompleted = true;
1402
- const error = err?.message || 'Unknown error';
1403
- console.error(`[TOOLS-API] Install error for ${toolId}:`, error);
1404
- logError('toolInstall', err, { toolId });
1405
- queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
1406
- broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error } });
1407
- queries.addToolInstallHistory(toolId, 'install', 'failed', error);
1408
- });
1409
- return;
1410
- }
1411
-
1412
- if (pathOnly.match(/^\/api\/tools\/([^/]+)\/update$/) && req.method === 'POST') {
1413
- const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/update$/)[1];
1414
- const body = await parseBody(req);
1415
- const tool = toolManager.getToolConfig(toolId);
1416
- if (!tool) {
1417
- sendJSON(req, res, 404, { error: 'Tool not found' });
1418
- return;
1419
- }
1420
- const current = await toolManager.checkToolStatusAsync(toolId);
1421
- if (!current || !current.installed) {
1422
- sendJSON(req, res, 400, { error: 'Tool not installed' });
1423
- return;
1424
- }
1425
- queries.updateToolStatus(toolId, { status: 'updating' });
1426
- sendJSON(req, res, 200, { success: true, updating: true });
1427
-
1428
- let updateCompleted = false;
1429
- const updateTimeout = setTimeout(() => {
1430
- if (!updateCompleted) {
1431
- updateCompleted = true;
1432
- queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Update timeout after 6 minutes' });
1433
- broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: 'Update timeout after 6 minutes' } });
1434
- queries.addToolInstallHistory(toolId, 'update', 'failed', 'Update timeout after 6 minutes');
1435
- }
1436
- }, 360000);
1437
-
1438
- toolManager.update(toolId, (msg) => {
1439
- broadcastSync({ type: 'tool_update_progress', toolId, data: msg });
1440
- }).then(async (result) => {
1441
- clearTimeout(updateTimeout);
1442
- if (updateCompleted) return;
1443
- updateCompleted = true;
1444
- if (result.success) {
1445
- const version = result.version || null;
1446
- console.log(`[TOOLS-API] Update succeeded for ${toolId}, version: ${version}`);
1447
- queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
1448
- const freshStatus = await toolManager.checkToolStatusAsync(toolId);
1449
- console.log(`[TOOLS-API] Fresh status after update for ${toolId}:`, JSON.stringify(freshStatus));
1450
- broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, version, ...freshStatus } });
1451
- queries.addToolInstallHistory(toolId, 'update', 'success', null);
1452
- } else {
1453
- console.error(`[TOOLS-API] Update failed for ${toolId}:`, result.error);
1454
- queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
1455
- broadcastSync({ type: 'tool_update_failed', toolId, data: result });
1456
- queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
1457
- }
1458
- }).catch((err) => {
1459
- clearTimeout(updateTimeout);
1460
- if (updateCompleted) return;
1461
- updateCompleted = true;
1462
- const error = err?.message || 'Unknown error';
1463
- console.error(`[TOOLS-API] Update error for ${toolId}:`, error);
1464
- logError('toolUpdate', err, { toolId });
1465
- queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
1466
- broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error } });
1467
- queries.addToolInstallHistory(toolId, 'update', 'failed', error);
1468
- });
1469
- return;
1470
- }
1471
-
1472
- if (pathOnly.match(/^\/api\/tools\/([^/]+)\/history$/) && req.method === 'GET') {
1473
- const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/history$/)[1];
1474
- const url = new URL(req.url, 'http://localhost');
1475
- const limit = Math.min(parseInt(url.searchParams.get('limit')) || 20, 100);
1476
- const offset = parseInt(url.searchParams.get('offset')) || 0;
1477
- const history = queries.getToolInstallHistory(toolId, limit, offset);
1478
- sendJSON(req, res, 200, { history });
1479
- return;
1480
- }
1481
-
1482
- if (pathOnly === '/api/tools/update' && req.method === 'POST') {
1483
- const allToolIds = ['cli-claude', 'cli-opencode', 'cli-gemini', 'cli-kilo', 'cli-codex', 'gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
1484
- sendJSON(req, res, 200, { updating: true, toolCount: allToolIds.length });
1485
- broadcastSync({ type: 'tools_update_started', tools: allToolIds });
1486
- setImmediate(async () => {
1487
- const toolIds = allToolIds;
1488
- const results = {};
1489
- for (const toolId of toolIds) {
1490
- try {
1491
- const result = await toolManager.update(toolId, (msg) => {
1492
- broadcastSync({ type: 'tool_update_progress', toolId, data: msg });
1493
- });
1494
- results[toolId] = result;
1495
- if (result.success) {
1496
- const version = result.version || null;
1497
- queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
1498
- queries.addToolInstallHistory(toolId, 'update', 'success', null);
1499
- const freshStatus = await toolManager.checkToolStatusAsync(toolId);
1500
- broadcastSync({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
1501
- } else {
1502
- queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
1503
- queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
1504
- broadcastSync({ type: 'tool_update_failed', toolId, data: result });
1505
- }
1506
- } catch (err) {
1507
- queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
1508
- queries.addToolInstallHistory(toolId, 'update', 'failed', err.message);
1509
- broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: err.message } });
1510
- }
1511
- }
1512
- broadcastSync({ type: 'tools_update_complete', data: results });
1513
- });
1514
- return;
1515
- }
1516
-
1517
- if (pathOnly === '/api/tools/refresh-all' && req.method === 'POST') {
1518
- sendJSON(req, res, 200, { refreshing: true, toolCount: 4 });
1519
- broadcastSync({ type: 'tools_refresh_started' });
1520
- setImmediate(async () => {
1521
- const tools = toolManager.getAllTools();
1522
- for (const tool of tools) {
1523
- queries.updateToolStatus(tool.id, {
1524
- status: tool.installed ? 'installed' : 'not_installed',
1525
- version: tool.installedVersion,
1526
- last_check_at: Date.now()
1527
- });
1528
- if (tool.installed) {
1529
- const status = await toolManager.checkToolStatusAsync(tool.id);
1530
- if (status && status.upgradeNeeded) {
1531
- queries.updateToolStatus(tool.id, { update_available: 1, latest_version: status.publishedVersion });
1532
- }
1533
- }
1534
- }
1535
- broadcastSync({ type: 'tools_refresh_complete', data: tools });
1536
- });
1537
- return;
1538
- }
1282
+ const toolHandler = _toolRoutes._match(req.method, pathOnly);
1283
+ if (toolHandler) { await toolHandler(req, res); return; }
1539
1284
 
1540
1285
  if (pathOnly === '/api/ws-stats' && req.method === 'GET') {
1541
1286
  const stats = wsOptimizer.getStats();
@@ -3326,6 +3071,7 @@ initSpeechManager({ broadcastSync, syncClients, queries });
3326
3071
  const _speechRoutes = registerSpeechRoutes({ sendJSON, parseBody, broadcastSync, debugLog });
3327
3072
  const _oauthRoutes = registerOAuthRoutes({ sendJSON, parseBody, PORT, BASE_URL, rootDir });
3328
3073
  const _utilRoutes = registerUtilRoutes({ sendJSON, parseBody, queries, STARTUP_CWD, PKG_VERSION });
3074
+ const _toolRoutes = registerToolRoutes({ sendJSON, parseBody, queries, broadcastSync, logError, toolManager });
3329
3075
 
3330
3076
  registerConvHandlers(wsRouter, {
3331
3077
  queries, activeExecutions, rateLimitState,