agentgui 1.0.210 → 1.0.212
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/database.js +18 -0
- package/package.json +1 -1
- package/server.js +46 -13
- package/static/index.html +79 -0
- package/static/js/agent-auth.js +2 -1
- package/static/js/client.js +299 -44
- package/static/js/websocket-manager.js +220 -216
package/database.js
CHANGED
|
@@ -1120,6 +1120,24 @@ export const queries = {
|
|
|
1120
1120
|
});
|
|
1121
1121
|
},
|
|
1122
1122
|
|
|
1123
|
+
getChunksSinceSeq(sessionId, sinceSeq) {
|
|
1124
|
+
const stmt = prep(
|
|
1125
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
1126
|
+
FROM chunks WHERE sessionId = ? AND sequence > ? ORDER BY sequence ASC`
|
|
1127
|
+
);
|
|
1128
|
+
const rows = stmt.all(sessionId, sinceSeq);
|
|
1129
|
+
return rows.map(row => {
|
|
1130
|
+
try {
|
|
1131
|
+
return {
|
|
1132
|
+
...row,
|
|
1133
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data
|
|
1134
|
+
};
|
|
1135
|
+
} catch (e) {
|
|
1136
|
+
return row;
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
},
|
|
1140
|
+
|
|
1123
1141
|
deleteSessionChunks(sessionId) {
|
|
1124
1142
|
const stmt = prep('DELETE FROM chunks WHERE sessionId = ?');
|
|
1125
1143
|
const result = stmt.run(sessionId);
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -246,6 +246,9 @@ function extractOAuthFromFile(oauth2Path) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
function getGeminiOAuthCreds() {
|
|
249
|
+
if (process.env.GOOGLE_OAUTH_CLIENT_ID && process.env.GOOGLE_OAUTH_CLIENT_SECRET) {
|
|
250
|
+
return { clientId: process.env.GOOGLE_OAUTH_CLIENT_ID, clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET, custom: true };
|
|
251
|
+
}
|
|
249
252
|
const oauthRelPath = path.join('node_modules', '@google', 'gemini-cli-core', 'dist', 'src', 'code_assist', 'oauth2.js');
|
|
250
253
|
try {
|
|
251
254
|
const geminiPath = findCommand('gemini');
|
|
@@ -333,13 +336,24 @@ function geminiOAuthResultPage(title, message, success) {
|
|
|
333
336
|
</div></body></html>`;
|
|
334
337
|
}
|
|
335
338
|
|
|
336
|
-
|
|
339
|
+
function isRemoteRequest(req) {
|
|
340
|
+
return !!(req && (req.headers['x-forwarded-for'] || req.headers['x-forwarded-host'] || req.headers['x-forwarded-proto']));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function startGeminiOAuth(req) {
|
|
337
344
|
const creds = getGeminiOAuthCreds();
|
|
338
345
|
if (!creds) throw new Error('Could not find Gemini CLI OAuth credentials. Install gemini CLI first.');
|
|
339
346
|
|
|
340
|
-
const
|
|
341
|
-
const
|
|
347
|
+
const useCustomClient = !!creds.custom;
|
|
348
|
+
const remote = isRemoteRequest(req);
|
|
349
|
+
let redirectUri;
|
|
350
|
+
if (useCustomClient && req) {
|
|
351
|
+
redirectUri = `${buildBaseUrl(req)}${BASE_URL}/oauth2callback`;
|
|
352
|
+
} else {
|
|
353
|
+
redirectUri = `http://localhost:${PORT}${BASE_URL}/oauth2callback`;
|
|
354
|
+
}
|
|
342
355
|
|
|
356
|
+
const state = crypto.randomBytes(32).toString('hex');
|
|
343
357
|
const client = new OAuth2Client({
|
|
344
358
|
clientId: creds.clientId,
|
|
345
359
|
clientSecret: creds.clientSecret,
|
|
@@ -352,6 +366,7 @@ async function startGeminiOAuth() {
|
|
|
352
366
|
state,
|
|
353
367
|
});
|
|
354
368
|
|
|
369
|
+
const mode = useCustomClient ? 'custom' : (remote ? 'cli-remote' : 'cli-local');
|
|
355
370
|
geminiOAuthPending = { client, redirectUri, state };
|
|
356
371
|
geminiOAuthState = { status: 'pending', error: null, email: null };
|
|
357
372
|
|
|
@@ -362,7 +377,7 @@ async function startGeminiOAuth() {
|
|
|
362
377
|
}
|
|
363
378
|
}, 5 * 60 * 1000);
|
|
364
379
|
|
|
365
|
-
return authUrl;
|
|
380
|
+
return { authUrl, mode };
|
|
366
381
|
}
|
|
367
382
|
|
|
368
383
|
async function exchangeGeminiOAuthCode(code, state) {
|
|
@@ -926,9 +941,15 @@ const server = http.createServer(async (req, res) => {
|
|
|
926
941
|
if (!sess) { sendJSON(req, res, 404, { error: 'Session not found' }); return; }
|
|
927
942
|
|
|
928
943
|
const url = new URL(req.url, 'http://localhost');
|
|
944
|
+
const sinceSeq = parseInt(url.searchParams.get('sinceSeq') || '-1');
|
|
929
945
|
const since = parseInt(url.searchParams.get('since') || '0');
|
|
930
946
|
|
|
931
|
-
|
|
947
|
+
let chunks;
|
|
948
|
+
if (sinceSeq >= 0) {
|
|
949
|
+
chunks = queries.getChunksSinceSeq(sessionId, sinceSeq);
|
|
950
|
+
} else {
|
|
951
|
+
chunks = queries.getChunksSince(sessionId, since);
|
|
952
|
+
}
|
|
932
953
|
sendJSON(req, res, 200, { ok: true, chunks });
|
|
933
954
|
return;
|
|
934
955
|
}
|
|
@@ -1130,8 +1151,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1130
1151
|
|
|
1131
1152
|
if (pathOnly === '/api/gemini-oauth/start' && req.method === 'POST') {
|
|
1132
1153
|
try {
|
|
1133
|
-
const
|
|
1134
|
-
sendJSON(req, res, 200, { authUrl });
|
|
1154
|
+
const result = await startGeminiOAuth(req);
|
|
1155
|
+
sendJSON(req, res, 200, { authUrl: result.authUrl, mode: result.mode });
|
|
1135
1156
|
} catch (e) {
|
|
1136
1157
|
console.error('[gemini-oauth] /api/gemini-oauth/start failed:', e);
|
|
1137
1158
|
sendJSON(req, res, 500, { error: e.message });
|
|
@@ -1188,10 +1209,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
1188
1209
|
|
|
1189
1210
|
if (agentId === 'gemini') {
|
|
1190
1211
|
try {
|
|
1191
|
-
const
|
|
1212
|
+
const result = await startGeminiOAuth(req);
|
|
1192
1213
|
const conversationId = '__agent_auth__';
|
|
1193
1214
|
broadcastSync({ type: 'script_started', conversationId, script: 'auth-gemini', agentId: 'gemini', timestamp: Date.now() });
|
|
1194
|
-
broadcastSync({ type: 'script_output', conversationId, data: `\x1b[36mOpening Google OAuth in your browser...\x1b[0m\r\n\r\nIf it doesn't open automatically, visit:\r\n${authUrl}\r\n`, stream: 'stdout', timestamp: Date.now() });
|
|
1215
|
+
broadcastSync({ type: 'script_output', conversationId, data: `\x1b[36mOpening Google OAuth in your browser...\x1b[0m\r\n\r\nIf it doesn't open automatically, visit:\r\n${result.authUrl}\r\n`, stream: 'stdout', timestamp: Date.now() });
|
|
1195
1216
|
|
|
1196
1217
|
const pollId = setInterval(() => {
|
|
1197
1218
|
if (geminiOAuthState.status === 'success') {
|
|
@@ -1208,7 +1229,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1208
1229
|
|
|
1209
1230
|
setTimeout(() => clearInterval(pollId), 5 * 60 * 1000);
|
|
1210
1231
|
|
|
1211
|
-
sendJSON(req, res, 200, { ok: true, agentId, authUrl });
|
|
1232
|
+
sendJSON(req, res, 200, { ok: true, agentId, authUrl: result.authUrl, mode: result.mode });
|
|
1212
1233
|
return;
|
|
1213
1234
|
} catch (e) {
|
|
1214
1235
|
console.error('[gemini-oauth] /api/agents/gemini/auth failed:', e);
|
|
@@ -1696,6 +1717,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1696
1717
|
conversationId,
|
|
1697
1718
|
block: systemBlock,
|
|
1698
1719
|
blockIndex: allBlocks.length,
|
|
1720
|
+
seq: currentSequence,
|
|
1699
1721
|
timestamp: Date.now()
|
|
1700
1722
|
});
|
|
1701
1723
|
} else if (parsed.type === 'assistant' && parsed.message?.content) {
|
|
@@ -1711,6 +1733,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1711
1733
|
conversationId,
|
|
1712
1734
|
block,
|
|
1713
1735
|
blockIndex: allBlocks.length - 1,
|
|
1736
|
+
seq: currentSequence,
|
|
1714
1737
|
timestamp: Date.now()
|
|
1715
1738
|
});
|
|
1716
1739
|
|
|
@@ -1737,6 +1760,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1737
1760
|
conversationId,
|
|
1738
1761
|
block: toolResultBlock,
|
|
1739
1762
|
blockIndex: allBlocks.length,
|
|
1763
|
+
seq: currentSequence,
|
|
1740
1764
|
timestamp: Date.now()
|
|
1741
1765
|
});
|
|
1742
1766
|
}
|
|
@@ -1762,6 +1786,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1762
1786
|
block: resultBlock,
|
|
1763
1787
|
blockIndex: allBlocks.length,
|
|
1764
1788
|
isResult: true,
|
|
1789
|
+
seq: currentSequence,
|
|
1765
1790
|
timestamp: Date.now()
|
|
1766
1791
|
});
|
|
1767
1792
|
|
|
@@ -1812,6 +1837,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1812
1837
|
sessionId,
|
|
1813
1838
|
conversationId,
|
|
1814
1839
|
eventCount,
|
|
1840
|
+
seq: currentSequence,
|
|
1815
1841
|
timestamp: Date.now()
|
|
1816
1842
|
});
|
|
1817
1843
|
|
|
@@ -2051,9 +2077,13 @@ wss.on('connection', (ws, req) => {
|
|
|
2051
2077
|
}));
|
|
2052
2078
|
} else if (data.type === 'set_voice') {
|
|
2053
2079
|
ws.ttsVoiceId = data.voiceId || 'default';
|
|
2080
|
+
} else if (data.type === 'latency_report') {
|
|
2081
|
+
ws.latencyTier = data.quality || 'good';
|
|
2082
|
+
ws.latencyAvg = data.avg || 0;
|
|
2054
2083
|
} else if (data.type === 'ping') {
|
|
2055
2084
|
ws.send(JSON.stringify({
|
|
2056
2085
|
type: 'pong',
|
|
2086
|
+
requestId: data.requestId,
|
|
2057
2087
|
timestamp: Date.now()
|
|
2058
2088
|
}));
|
|
2059
2089
|
}
|
|
@@ -2082,13 +2112,16 @@ wss.on('connection', (ws, req) => {
|
|
|
2082
2112
|
const BROADCAST_TYPES = new Set([
|
|
2083
2113
|
'message_created', 'conversation_created', 'conversation_updated',
|
|
2084
2114
|
'conversations_updated', 'conversation_deleted', 'queue_status', 'queue_updated',
|
|
2085
|
-
'streaming_start', 'streaming_complete', 'streaming_error',
|
|
2086
2115
|
'rate_limit_hit', 'rate_limit_clear',
|
|
2087
2116
|
'script_started', 'script_stopped', 'script_output'
|
|
2088
2117
|
]);
|
|
2089
2118
|
|
|
2090
2119
|
const wsBatchQueues = new Map();
|
|
2091
|
-
const
|
|
2120
|
+
const BATCH_BY_TIER = { excellent: 16, good: 32, fair: 50, poor: 100, bad: 200 };
|
|
2121
|
+
|
|
2122
|
+
function getBatchInterval(ws) {
|
|
2123
|
+
return BATCH_BY_TIER[ws.latencyTier] || 32;
|
|
2124
|
+
}
|
|
2092
2125
|
|
|
2093
2126
|
function flushWsBatch(ws) {
|
|
2094
2127
|
const queue = wsBatchQueues.get(ws);
|
|
@@ -2109,7 +2142,7 @@ function sendToClient(ws, data) {
|
|
|
2109
2142
|
if (!queue) { queue = { msgs: [], timer: null }; wsBatchQueues.set(ws, queue); }
|
|
2110
2143
|
queue.msgs.push(data);
|
|
2111
2144
|
if (!queue.timer) {
|
|
2112
|
-
queue.timer = setTimeout(() => flushWsBatch(ws),
|
|
2145
|
+
queue.timer = setTimeout(() => flushWsBatch(ws), getBatchInterval(ws));
|
|
2113
2146
|
}
|
|
2114
2147
|
}
|
|
2115
2148
|
|
package/static/index.html
CHANGED
|
@@ -2213,6 +2213,85 @@
|
|
|
2213
2213
|
}
|
|
2214
2214
|
|
|
2215
2215
|
html.dark .event-streaming-complete { background: linear-gradient(135deg, #0a1f0f, #0f2b1a); }
|
|
2216
|
+
|
|
2217
|
+
@keyframes skeleton-pulse {
|
|
2218
|
+
0%, 100% { opacity: 0.4; }
|
|
2219
|
+
50% { opacity: 0.15; }
|
|
2220
|
+
}
|
|
2221
|
+
.skeleton-pulse { animation: skeleton-pulse 1.5s ease-in-out infinite; }
|
|
2222
|
+
.skeleton-loading { padding: 1rem; }
|
|
2223
|
+
|
|
2224
|
+
.message-sending { opacity: 0.6; transition: opacity 0.3s ease; }
|
|
2225
|
+
.message-send-failed { border-left: 3px solid var(--color-error); }
|
|
2226
|
+
|
|
2227
|
+
.connection-indicator {
|
|
2228
|
+
display: inline-flex;
|
|
2229
|
+
align-items: center;
|
|
2230
|
+
gap: 0.375rem;
|
|
2231
|
+
padding: 0.25rem 0.5rem;
|
|
2232
|
+
border-radius: 1rem;
|
|
2233
|
+
font-size: 0.75rem;
|
|
2234
|
+
cursor: pointer;
|
|
2235
|
+
transition: all 0.3s ease;
|
|
2236
|
+
}
|
|
2237
|
+
.connection-dot {
|
|
2238
|
+
width: 8px;
|
|
2239
|
+
height: 8px;
|
|
2240
|
+
border-radius: 50%;
|
|
2241
|
+
transition: background-color 0.5s ease;
|
|
2242
|
+
}
|
|
2243
|
+
.connection-dot.excellent { background: #10b981; }
|
|
2244
|
+
.connection-dot.good { background: #10b981; }
|
|
2245
|
+
.connection-dot.fair { background: #f59e0b; }
|
|
2246
|
+
.connection-dot.poor { background: #f97316; }
|
|
2247
|
+
.connection-dot.bad { background: #ef4444; }
|
|
2248
|
+
.connection-dot.unknown { background: #6b7280; }
|
|
2249
|
+
.connection-dot.disconnected { background: #ef4444; animation: pulse 1.5s ease-in-out infinite; }
|
|
2250
|
+
.connection-dot.reconnecting { background: #f59e0b; animation: pulse 1s ease-in-out infinite; }
|
|
2251
|
+
|
|
2252
|
+
.connection-tooltip {
|
|
2253
|
+
position: absolute;
|
|
2254
|
+
top: 100%;
|
|
2255
|
+
right: 0;
|
|
2256
|
+
margin-top: 0.5rem;
|
|
2257
|
+
padding: 0.75rem;
|
|
2258
|
+
background: var(--color-bg-secondary);
|
|
2259
|
+
border: 1px solid var(--color-border);
|
|
2260
|
+
border-radius: 0.5rem;
|
|
2261
|
+
font-size: 0.75rem;
|
|
2262
|
+
white-space: nowrap;
|
|
2263
|
+
z-index: 100;
|
|
2264
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
.new-content-pill {
|
|
2268
|
+
position: sticky;
|
|
2269
|
+
bottom: 0.5rem;
|
|
2270
|
+
left: 50%;
|
|
2271
|
+
transform: translateX(-50%);
|
|
2272
|
+
padding: 0.375rem 1rem;
|
|
2273
|
+
background: var(--color-primary);
|
|
2274
|
+
color: white;
|
|
2275
|
+
border: none;
|
|
2276
|
+
border-radius: 1rem;
|
|
2277
|
+
font-size: 0.8rem;
|
|
2278
|
+
cursor: pointer;
|
|
2279
|
+
z-index: 50;
|
|
2280
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
2281
|
+
transition: opacity 0.2s ease;
|
|
2282
|
+
}
|
|
2283
|
+
.new-content-pill:hover { opacity: 0.9; }
|
|
2284
|
+
|
|
2285
|
+
@keyframes block-appear {
|
|
2286
|
+
from { opacity: 0; transform: translateY(6px); }
|
|
2287
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2288
|
+
}
|
|
2289
|
+
.streaming-blocks > * {
|
|
2290
|
+
animation: block-appear 0.2s ease-out both;
|
|
2291
|
+
}
|
|
2292
|
+
.streaming-blocks > details.block-tool-use {
|
|
2293
|
+
transition: max-height 0.3s ease;
|
|
2294
|
+
}
|
|
2216
2295
|
</style>
|
|
2217
2296
|
</head>
|
|
2218
2297
|
<body>
|
package/static/js/agent-auth.js
CHANGED
|
@@ -217,7 +217,8 @@
|
|
|
217
217
|
if (data.authUrl) {
|
|
218
218
|
window.open(data.authUrl, '_blank');
|
|
219
219
|
if (agentId === 'gemini') {
|
|
220
|
-
|
|
220
|
+
var needsPaste = data.mode === 'cli-remote';
|
|
221
|
+
if (needsPaste) showOAuthPasteModal();
|
|
221
222
|
cleanupOAuthPolling();
|
|
222
223
|
oauthPollInterval = setInterval(function() {
|
|
223
224
|
fetch(BASE + '/api/gemini-oauth/status').then(function(r) { return r.json(); }).then(function(status) {
|