agentgui 1.0.770 → 1.0.772
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 +1 -0
- package/lib/routes-agents.js +108 -0
- package/package.json +1 -1
- package/server.js +7 -168
package/CLAUDE.md
CHANGED
|
@@ -50,6 +50,7 @@ lib/routes-speech.js Speech/TTS HTTP route handlers (stt, tts, voices, speech-
|
|
|
50
50
|
lib/routes-oauth.js OAuth HTTP route handlers (gemini-oauth/*, codex-oauth/*)
|
|
51
51
|
lib/routes-tools.js Tool management HTTP route handlers (list, install, update, history, refresh)
|
|
52
52
|
lib/routes-util.js Utility HTTP route handlers (clone, folders, git, home, version, import)
|
|
53
|
+
lib/routes-agents.js Agent list/search/auth-status/descriptor/models HTTP route handlers
|
|
53
54
|
lib/routes-conversations.js Conversation CRUD HTTP route handlers (list, create, get, update, delete, archive, restore)
|
|
54
55
|
lib/routes-debug.js Debug/backup/restore/ws-stats HTTP route handlers
|
|
55
56
|
lib/routes-threads.js Thread CRUD HTTP route handlers (ACP v0.2.3 thread API)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { getAgentDescriptor } from './agent-descriptors.js';
|
|
6
|
+
import { discoverExternalACPServers } from './agent-discovery.js';
|
|
7
|
+
|
|
8
|
+
export function register(deps) {
|
|
9
|
+
const { sendJSON, parseBody, queries, discoveredAgents, getACPStatus, modelCache, getModelsForAgent, debugLog } = deps;
|
|
10
|
+
const routes = {};
|
|
11
|
+
|
|
12
|
+
routes['GET /api/agents'] = async (req, res) => {
|
|
13
|
+
debugLog(`[API /api/agents] Returning ${discoveredAgents.length} agents`);
|
|
14
|
+
sendJSON(req, res, 200, { agents: discoveredAgents });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
routes['GET /api/acp/status'] = async (req, res) => {
|
|
18
|
+
sendJSON(req, res, 200, { tools: getACPStatus() });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
routes['POST /api/agents/search'] = async (req, res) => {
|
|
22
|
+
const body = await parseBody(req);
|
|
23
|
+
try {
|
|
24
|
+
const localResult = queries.searchAgents(discoveredAgents, body);
|
|
25
|
+
const externalAgents = await discoverExternalACPServers(discoveredAgents);
|
|
26
|
+
const externalResult = queries.searchAgents(externalAgents, body);
|
|
27
|
+
sendJSON(req, res, 200, {
|
|
28
|
+
agents: [...localResult.agents, ...externalResult.agents],
|
|
29
|
+
total: localResult.total + externalResult.total,
|
|
30
|
+
limit: body.limit || 50, offset: body.offset || 0,
|
|
31
|
+
hasMore: localResult.hasMore || externalResult.hasMore,
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error searching agents:', error);
|
|
35
|
+
sendJSON(req, res, 200, queries.searchAgents(discoveredAgents, body));
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
routes['GET /api/agents/auth-status'] = async (req, res) => {
|
|
40
|
+
const statuses = discoveredAgents.map(agent => {
|
|
41
|
+
const status = { id: agent.id, name: agent.name, authenticated: false, detail: '' };
|
|
42
|
+
try {
|
|
43
|
+
if (agent.id === 'claude-code') {
|
|
44
|
+
const credFile = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
45
|
+
if (fs.existsSync(credFile)) {
|
|
46
|
+
const creds = JSON.parse(fs.readFileSync(credFile, 'utf-8'));
|
|
47
|
+
if (creds.claudeAiOauth && creds.claudeAiOauth.expiresAt > Date.now()) {
|
|
48
|
+
status.authenticated = true;
|
|
49
|
+
status.detail = creds.claudeAiOauth.subscriptionType || 'authenticated';
|
|
50
|
+
} else { status.detail = 'expired'; }
|
|
51
|
+
} else { status.detail = 'no credentials'; }
|
|
52
|
+
} else if (agent.id === 'gemini') {
|
|
53
|
+
const oauthFile = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
|
|
54
|
+
const acctFile = path.join(os.homedir(), '.gemini', 'google_accounts.json');
|
|
55
|
+
let hasOAuth = false;
|
|
56
|
+
if (fs.existsSync(oauthFile)) {
|
|
57
|
+
try { const creds = JSON.parse(fs.readFileSync(oauthFile, 'utf-8')); if (creds.refresh_token || creds.access_token) hasOAuth = true; } catch (_) {}
|
|
58
|
+
}
|
|
59
|
+
if (fs.existsSync(acctFile)) {
|
|
60
|
+
const accts = JSON.parse(fs.readFileSync(acctFile, 'utf-8'));
|
|
61
|
+
if (accts.active) { status.authenticated = true; status.detail = accts.active; }
|
|
62
|
+
else if (hasOAuth) { status.authenticated = true; status.detail = 'oauth'; }
|
|
63
|
+
else { status.detail = 'logged out'; }
|
|
64
|
+
} else if (hasOAuth) { status.authenticated = true; status.detail = 'oauth'; }
|
|
65
|
+
else { status.detail = 'no credentials'; }
|
|
66
|
+
} else if (agent.id === 'opencode') {
|
|
67
|
+
const out = execSync('opencode auth list 2>&1', { encoding: 'utf-8', timeout: 5000 });
|
|
68
|
+
const countMatch = out.match(/(\d+)\s+credentials?/);
|
|
69
|
+
if (countMatch && parseInt(countMatch[1], 10) > 0) { status.authenticated = true; status.detail = countMatch[1] + ' credential(s)'; }
|
|
70
|
+
else { status.detail = 'no credentials'; }
|
|
71
|
+
} else { status.detail = 'unknown'; }
|
|
72
|
+
} catch (e) { status.detail = 'check failed'; }
|
|
73
|
+
return status;
|
|
74
|
+
});
|
|
75
|
+
sendJSON(req, res, 200, { agents: statuses });
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
routes['_match'] = (method, pathOnly) => {
|
|
79
|
+
const key = `${method} ${pathOnly}`;
|
|
80
|
+
if (routes[key]) return routes[key];
|
|
81
|
+
let m;
|
|
82
|
+
if (method === 'GET' && (m = pathOnly.match(/^\/api\/agents\/([^/]+)$/)))
|
|
83
|
+
return (req, res) => handleGetAgent(req, res, m[1]);
|
|
84
|
+
if (method === 'GET' && (m = pathOnly.match(/^\/api\/agents\/([^/]+)\/descriptor$/)))
|
|
85
|
+
return (req, res) => { const d = getAgentDescriptor(m[1]); d ? sendJSON(req, res, 200, d) : sendJSON(req, res, 404, { error: 'Agent not found' }); };
|
|
86
|
+
if (method === 'GET' && (m = pathOnly.match(/^\/api\/agents\/([^/]+)\/models$/)))
|
|
87
|
+
return (req, res) => handleGetModels(req, res, m[1]);
|
|
88
|
+
return null;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
async function handleGetAgent(req, res, agentId) {
|
|
92
|
+
const agent = discoveredAgents.find(a => a.id === agentId);
|
|
93
|
+
if (!agent) { sendJSON(req, res, 404, { error: 'Agent not found' }); return; }
|
|
94
|
+
sendJSON(req, res, 200, { id: agent.id, name: agent.name, description: agent.description || '', icon: agent.icon || null, status: 'available' });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function handleGetModels(req, res, agentId) {
|
|
98
|
+
const cached = modelCache.get(agentId);
|
|
99
|
+
if (cached && (Date.now() - cached.timestamp) < 300000) { sendJSON(req, res, 200, { models: cached.models }); return; }
|
|
100
|
+
try {
|
|
101
|
+
const models = await getModelsForAgent(agentId);
|
|
102
|
+
modelCache.set(agentId, { models, timestamp: Date.now() });
|
|
103
|
+
sendJSON(req, res, 200, { models });
|
|
104
|
+
} catch (err) { sendJSON(req, res, 200, { models: [] }); }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return routes;
|
|
108
|
+
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -25,6 +25,7 @@ import { register as registerToolRoutes } from './lib/routes-tools.js';
|
|
|
25
25
|
import { register as registerThreadRoutes } from './lib/routes-threads.js';
|
|
26
26
|
import { register as registerDebugRoutes } from './lib/routes-debug.js';
|
|
27
27
|
import { register as registerConvRoutes } from './lib/routes-conversations.js';
|
|
28
|
+
import { register as registerAgentRoutes } from './lib/routes-agents.js';
|
|
28
29
|
import { startCodexOAuth, exchangeCodexOAuthCode, handleCodexOAuthCallback, getCodexOAuthStatus, getCodexOAuthState, CODEX_HOME, CODEX_AUTH_FILE } from './lib/oauth-codex.js';
|
|
29
30
|
import { WSOptimizer } from './lib/ws-optimizer.js';
|
|
30
31
|
import { WsRouter } from './lib/ws-protocol.js';
|
|
@@ -83,12 +84,9 @@ function buildSystemPrompt(agentId, model, subAgent) {
|
|
|
83
84
|
const displayAgentId = agentId.split('-·-')[0];
|
|
84
85
|
parts.push(`Use ${displayAgentId} subagent for all tasks.`);
|
|
85
86
|
}
|
|
86
|
-
if (model) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (subAgent) {
|
|
90
|
-
parts.push(`Subagent: ${subAgent}.`);
|
|
91
|
-
}
|
|
87
|
+
if (model) parts.push(`Model: ${model}.`);
|
|
88
|
+
if (subAgent) parts.push(`Subagent: ${subAgent}.`);
|
|
89
|
+
parts.push('Be concise. Minimize unnecessary tool calls. Read files before editing. Prefer targeted searches over broad exploration.');
|
|
92
90
|
return parts.join(' ');
|
|
93
91
|
}
|
|
94
92
|
|
|
@@ -1109,168 +1107,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1109
1107
|
return;
|
|
1110
1108
|
}
|
|
1111
1109
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
sendJSON(req, res, 200, { agents: discoveredAgents });
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
if (pathOnly === '/api/acp/status' && req.method === 'GET') {
|
|
1119
|
-
sendJSON(req, res, 200, { tools: getACPStatus() });
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
const debugHandler = _debugRoutes._match(req.method, pathOnly);
|
|
1126
|
-
if (debugHandler) { await debugHandler(req, res); return; }
|
|
1127
|
-
|
|
1128
|
-
if (pathOnly === '/api/agents/search' && req.method === 'POST') {
|
|
1129
|
-
const body = await parseBody(req);
|
|
1130
|
-
try {
|
|
1131
|
-
// Get local agents
|
|
1132
|
-
const localResult = queries.searchAgents(discoveredAgents, body);
|
|
1133
|
-
|
|
1134
|
-
// Get external agents from ACP servers
|
|
1135
|
-
const externalAgents = await discoverExternalACPServers(discoveredAgents);
|
|
1136
|
-
const externalResult = queries.searchAgents(externalAgents, body);
|
|
1137
|
-
|
|
1138
|
-
// Combine results
|
|
1139
|
-
const combinedAgents = [...localResult.agents, ...externalResult.agents];
|
|
1140
|
-
const total = localResult.total + externalResult.total;
|
|
1141
|
-
const hasMore = localResult.hasMore || externalResult.hasMore;
|
|
1142
|
-
|
|
1143
|
-
sendJSON(req, res, 200, {
|
|
1144
|
-
agents: combinedAgents,
|
|
1145
|
-
total,
|
|
1146
|
-
limit: body.limit || 50,
|
|
1147
|
-
offset: body.offset || 0,
|
|
1148
|
-
hasMore,
|
|
1149
|
-
});
|
|
1150
|
-
} catch (error) {
|
|
1151
|
-
console.error('Error searching agents:', error);
|
|
1152
|
-
const result = queries.searchAgents(discoveredAgents, body);
|
|
1153
|
-
sendJSON(req, res, 200, result);
|
|
1154
|
-
}
|
|
1155
|
-
return;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
if (pathOnly === '/api/agents/auth-status' && req.method === 'GET') {
|
|
1159
|
-
const statuses = discoveredAgents.map(agent => {
|
|
1160
|
-
const status = { id: agent.id, name: agent.name, authenticated: false, detail: '' };
|
|
1161
|
-
try {
|
|
1162
|
-
if (agent.id === 'claude-code') {
|
|
1163
|
-
const credFile = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
1164
|
-
if (fs.existsSync(credFile)) {
|
|
1165
|
-
const creds = JSON.parse(fs.readFileSync(credFile, 'utf-8'));
|
|
1166
|
-
if (creds.claudeAiOauth && creds.claudeAiOauth.expiresAt > Date.now()) {
|
|
1167
|
-
status.authenticated = true;
|
|
1168
|
-
status.detail = creds.claudeAiOauth.subscriptionType || 'authenticated';
|
|
1169
|
-
} else {
|
|
1170
|
-
status.detail = 'expired';
|
|
1171
|
-
}
|
|
1172
|
-
} else {
|
|
1173
|
-
status.detail = 'no credentials';
|
|
1174
|
-
}
|
|
1175
|
-
} else if (agent.id === 'gemini') {
|
|
1176
|
-
const oauthFile = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
|
|
1177
|
-
const acctFile = path.join(os.homedir(), '.gemini', 'google_accounts.json');
|
|
1178
|
-
let hasOAuth = false;
|
|
1179
|
-
if (fs.existsSync(oauthFile)) {
|
|
1180
|
-
try {
|
|
1181
|
-
const creds = JSON.parse(fs.readFileSync(oauthFile, 'utf-8'));
|
|
1182
|
-
if (creds.refresh_token || creds.access_token) hasOAuth = true;
|
|
1183
|
-
} catch (_) {}
|
|
1184
|
-
}
|
|
1185
|
-
if (fs.existsSync(acctFile)) {
|
|
1186
|
-
const accts = JSON.parse(fs.readFileSync(acctFile, 'utf-8'));
|
|
1187
|
-
if (accts.active) {
|
|
1188
|
-
status.authenticated = true;
|
|
1189
|
-
status.detail = accts.active;
|
|
1190
|
-
} else if (hasOAuth) {
|
|
1191
|
-
status.authenticated = true;
|
|
1192
|
-
status.detail = 'oauth';
|
|
1193
|
-
} else {
|
|
1194
|
-
status.detail = 'logged out';
|
|
1195
|
-
}
|
|
1196
|
-
} else if (hasOAuth) {
|
|
1197
|
-
status.authenticated = true;
|
|
1198
|
-
status.detail = 'oauth';
|
|
1199
|
-
} else {
|
|
1200
|
-
status.detail = 'no credentials';
|
|
1201
|
-
}
|
|
1202
|
-
} else if (agent.id === 'opencode') {
|
|
1203
|
-
const out = execSync('opencode auth list 2>&1', { encoding: 'utf-8', timeout: 5000 });
|
|
1204
|
-
const countMatch = out.match(/(\d+)\s+credentials?/);
|
|
1205
|
-
if (countMatch && parseInt(countMatch[1], 10) > 0) {
|
|
1206
|
-
status.authenticated = true;
|
|
1207
|
-
status.detail = countMatch[1] + ' credential(s)';
|
|
1208
|
-
} else {
|
|
1209
|
-
status.detail = 'no credentials';
|
|
1210
|
-
}
|
|
1211
|
-
} else {
|
|
1212
|
-
status.detail = 'unknown';
|
|
1213
|
-
}
|
|
1214
|
-
} catch (e) {
|
|
1215
|
-
status.detail = 'check failed';
|
|
1216
|
-
}
|
|
1217
|
-
return status;
|
|
1218
|
-
});
|
|
1219
|
-
sendJSON(req, res, 200, { agents: statuses });
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
const agentByIdMatch = pathOnly.match(/^\/api\/agents\/([^/]+)$/);
|
|
1224
|
-
if (agentByIdMatch && req.method === 'GET') {
|
|
1225
|
-
const agentId = agentByIdMatch[1];
|
|
1226
|
-
const agent = discoveredAgents.find(a => a.id === agentId);
|
|
1227
|
-
|
|
1228
|
-
if (!agent) {
|
|
1229
|
-
sendJSON(req, res, 404, { error: 'Agent not found' });
|
|
1230
|
-
return;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
sendJSON(req, res, 200, {
|
|
1234
|
-
id: agent.id,
|
|
1235
|
-
name: agent.name,
|
|
1236
|
-
description: agent.description || '',
|
|
1237
|
-
icon: agent.icon || null,
|
|
1238
|
-
status: 'available'
|
|
1239
|
-
});
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
const agentDescriptorMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/descriptor$/);
|
|
1244
|
-
if (agentDescriptorMatch && req.method === 'GET') {
|
|
1245
|
-
const agentId = agentDescriptorMatch[1];
|
|
1246
|
-
const descriptor = getAgentDescriptor(agentId);
|
|
1247
|
-
|
|
1248
|
-
if (!descriptor) {
|
|
1249
|
-
sendJSON(req, res, 404, { error: 'Agent not found' });
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
sendJSON(req, res, 200, descriptor);
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
const modelsMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/models$/);
|
|
1258
|
-
if (modelsMatch && req.method === 'GET') {
|
|
1259
|
-
const agentId = modelsMatch[1];
|
|
1260
|
-
const cached = modelCache.get(agentId);
|
|
1261
|
-
if (cached && (Date.now() - cached.timestamp) < 300000) {
|
|
1262
|
-
sendJSON(req, res, 200, { models: cached.models });
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
try {
|
|
1266
|
-
const models = await getModelsForAgent(agentId);
|
|
1267
|
-
modelCache.set(agentId, { models, timestamp: Date.now() });
|
|
1268
|
-
sendJSON(req, res, 200, { models });
|
|
1269
|
-
} catch (err) {
|
|
1270
|
-
sendJSON(req, res, 200, { models: [] });
|
|
1271
|
-
}
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1110
|
+
const agentHandler = _agentRoutes._match(req.method, pathOnly);
|
|
1111
|
+
if (agentHandler) { await agentHandler(req, res); return; }
|
|
1274
1112
|
|
|
1275
1113
|
if (pathOnly === '/api/runs' && req.method === 'POST') {
|
|
1276
1114
|
const body = await parseBody(req);
|
|
@@ -2766,6 +2604,7 @@ const _toolRoutes = registerToolRoutes({ sendJSON, parseBody, queries, broadcast
|
|
|
2766
2604
|
const _threadRoutes = registerThreadRoutes({ sendJSON, parseBody, queries });
|
|
2767
2605
|
const _debugRoutes = registerDebugRoutes({ sendJSON, queries, activeExecutions, messageQueues, syncClients, wsOptimizer, _errLogPath });
|
|
2768
2606
|
const _convRoutes = registerConvRoutes({ sendJSON, parseBody, queries, activeExecutions, broadcastSync });
|
|
2607
|
+
const _agentRoutes = registerAgentRoutes({ sendJSON, parseBody, queries, discoveredAgents, getACPStatus, modelCache, getModelsForAgent, debugLog });
|
|
2769
2608
|
|
|
2770
2609
|
registerConvHandlers(wsRouter, {
|
|
2771
2610
|
queries, activeExecutions, rateLimitState,
|