agentgui 1.0.760 → 1.0.762
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 +9 -2
- package/lib/routes-tools.js +185 -0
- package/package.json +1 -1
- package/server.js +4 -258
package/CLAUDE.md
CHANGED
|
@@ -46,6 +46,10 @@ lib/tool-manager.js Tool facade - re-exports from tool-version, tool-spawner,
|
|
|
46
46
|
lib/tool-version.js Version detection for CLI tools and plugins (data-driven framework paths)
|
|
47
47
|
lib/tool-spawner.js npm/bun install/update spawn with timeout and heartbeat
|
|
48
48
|
lib/tool-provisioner.js Auto-provisioning and periodic update checking
|
|
49
|
+
lib/routes-speech.js Speech/TTS HTTP route handlers (stt, tts, voices, speech-status)
|
|
50
|
+
lib/routes-oauth.js OAuth HTTP route handlers (gemini-oauth/*, codex-oauth/*)
|
|
51
|
+
lib/routes-tools.js Tool management HTTP route handlers (list, install, update, history, refresh)
|
|
52
|
+
lib/routes-util.js Utility HTTP route handlers (clone, folders, git, home, version, import)
|
|
49
53
|
lib/ws-protocol.js WebSocket RPC router (WsRouter class)
|
|
50
54
|
lib/ws-optimizer.js Per-client priority queue for WS event batching
|
|
51
55
|
lib/ws-handlers-conv.js Conversation CRUD, chunks, cancel, steer, inject RPC handlers
|
|
@@ -156,6 +160,9 @@ All routes are prefixed with `BASE_URL` (default `/gm`).
|
|
|
156
160
|
- `GET /api/conversations/:id` - Get conversation with streaming status
|
|
157
161
|
- `POST /api/conversations/:id` - Update conversation
|
|
158
162
|
- `DELETE /api/conversations/:id` - Delete conversation
|
|
163
|
+
- `POST /api/conversations/:id/archive` - Archive conversation (soft-delete)
|
|
164
|
+
- `POST /api/conversations/:id/restore` - Restore archived conversation
|
|
165
|
+
- `GET /api/conversations/archived` - List archived conversations
|
|
159
166
|
- `GET /api/conversations/:id/messages` - Get messages (query: limit, offset)
|
|
160
167
|
- `POST /api/conversations/:id/messages` - Send message (body: content, agentId)
|
|
161
168
|
- `POST /api/conversations/:id/stream` - Start streaming execution
|
|
@@ -268,12 +275,12 @@ Server broadcasts:
|
|
|
268
275
|
|
|
269
276
|
**WSOptimizer** (`lib/ws-optimizer.js`): Per-client priority queue. High-priority events flush immediately; normal/low batch by latency tier (16ms excellent → 200ms bad). Rate limit: 100 msg/sec — overflow is re-queued (not dropped). No `lastKey` deduplication (was removed — caused valid event drops).
|
|
270
277
|
|
|
271
|
-
### WS RPC Methods (
|
|
278
|
+
### WS RPC Methods (86 total)
|
|
272
279
|
|
|
273
280
|
**agent:** `agent.auth`, `agent.authstat`, `agent.desc`, `agent.get`, `agent.ls`, `agent.models`, `agent.search`, `agent.subagents`, `agent.update`
|
|
274
281
|
**auth:** `auth.configs`, `auth.save`
|
|
275
282
|
**codex:** `codex.complete`, `codex.relay`, `codex.start`, `codex.status`
|
|
276
|
-
**conv:** `conv.cancel`, `conv.chunks`, `conv.chunks.earlier`, `conv.del`, `conv.del.all`, `conv.export`, `conv.full`, `conv.get`, `conv.inject`, `conv.ls`, `conv.new`, `conv.prune`, `conv.run-script`, `conv.scripts`, `conv.search`, `conv.steer`, `conv.stop-script`, `conv.sync`, `conv.tags`, `conv.upd`
|
|
283
|
+
**conv:** `conv.cancel`, `conv.chunks`, `conv.chunks.earlier`, `conv.del`, `conv.del.all`, `conv.export`, `conv.full`, `conv.get`, `conv.import`, `conv.inject`, `conv.ls`, `conv.new`, `conv.prune`, `conv.run-script`, `conv.scripts`, `conv.search`, `conv.steer`, `conv.stop-script`, `conv.sync`, `conv.tags`, `conv.upd`
|
|
277
284
|
**gemini:** `gemini.complete`, `gemini.relay`, `gemini.start`, `gemini.status`
|
|
278
285
|
**git:** `git.check`, `git.push`
|
|
279
286
|
**msg:** `msg.get`, `msg.ls`, `msg.ls.earlier`, `msg.send`, `msg.stream`
|
|
@@ -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
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
|
-
|
|
1282
|
-
|
|
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,
|