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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.210",
3
+ "version": "1.0.212",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
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
- async function startGeminiOAuth() {
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 redirectUri = `http://localhost:${PORT}${BASE_URL}/oauth2callback`;
341
- const state = crypto.randomBytes(32).toString('hex');
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
- const chunks = queries.getChunksSince(sessionId, since);
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 authUrl = await startGeminiOAuth();
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 authUrl = await startGeminiOAuth();
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 WS_BATCH_INTERVAL = 16;
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), WS_BATCH_INTERVAL);
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>
@@ -217,7 +217,8 @@
217
217
  if (data.authUrl) {
218
218
  window.open(data.authUrl, '_blank');
219
219
  if (agentId === 'gemini') {
220
- showOAuthPasteModal();
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) {