agentgui 1.0.794 → 1.0.796

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +41 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.794",
3
+ "version": "1.0.796",
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
@@ -8,6 +8,7 @@ import { WebSocketServer } from 'ws';
8
8
  import { execSync, spawn } from 'child_process';
9
9
  import { LRUCache } from 'lru-cache';
10
10
  import { createRequire } from 'module';
11
+ import crypto from 'crypto';
11
12
  const PKG_VERSION = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version;
12
13
  import express from 'express';
13
14
  import Busboy from 'busboy';
@@ -59,20 +60,7 @@ process.on('unhandledRejection', (reason, promise) => {
59
60
  if (reason instanceof Error) console.error(reason.stack);
60
61
  });
61
62
 
62
- function gracefulShutdown(signal) {
63
- console.log(`[SIGNAL] ${signal} received - graceful shutdown`);
64
- try { pm2Manager.disconnect(); } catch (_) {}
65
- if (jsonlWatcher) try { jsonlWatcher.stop(); } catch (_) {}
66
- for (const [convId, entry] of activeExecutions) {
67
- try { if (entry.pid) process.kill(entry.pid, 'SIGTERM'); } catch (_) {}
68
- }
69
- stopACPTools().catch(() => {}).finally(() => {
70
- try { wss.close(() => server.close(() => process.exit(0))); } catch (_) { process.exit(0); }
71
- setTimeout(() => process.exit(1), 5000);
72
- });
73
- }
74
- process.on('SIGINT', () => gracefulShutdown('SIGINT'));
75
- process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
63
+ // Signal handlers registered after server initialization (see bottom of file)
76
64
  process.on('SIGHUP', () => { console.log('[SIGNAL] SIGHUP received (ignored - uncrashable)'); });
77
65
  process.on('beforeExit', (code) => { console.log('[PROCESS] beforeExit with code:', code); });
78
66
  process.on('exit', (code) => { console.log('[PROCESS] exit with code:', code); });
@@ -469,7 +457,7 @@ const server = http.createServer(async (req, res) => {
469
457
  try {
470
458
  const _decoded = Buffer.from(_auth.slice(6), 'base64').toString('utf8');
471
459
  const _ci = _decoded.indexOf(':');
472
- if (_ci !== -1) _ok = _decoded.slice(_ci + 1) === _pwd;
460
+ if (_ci !== -1) { const _p = _decoded.slice(_ci + 1); try { _ok = _p.length === _pwd.length && crypto.timingSafeEqual(Buffer.from(_p), Buffer.from(_pwd)); } catch { _ok = false; } }
473
461
  } catch (_) {}
474
462
  }
475
463
  if (!_ok) {
@@ -1966,10 +1954,21 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
1966
1954
  }
1967
1955
 
1968
1956
  // Set flag to stop processing and trigger retry
1969
- rateLimitState.set(conversationId, {
1970
- retryAt: Date.now() + (retryAfterSec * 1000),
1971
- cooldownMs: retryAfterSec * 1000,
1972
- retryCount: 0,
1957
+ const existingRetryCount = rateLimitState.get(conversationId)?.retryCount || 0;
1958
+ if (existingRetryCount >= 3) {
1959
+ debugLog(`[rate-limit] Conv ${conversationId} stream rate limit hit ${existingRetryCount + 1} times, giving up`);
1960
+ batcher.drain();
1961
+ activeExecutions.delete(conversationId);
1962
+ queries.setIsStreaming(conversationId, false);
1963
+ const errorMessage = queries.createMessage(conversationId, 'assistant', `Error: Rate limit exceeded after ${existingRetryCount + 1} attempts. Please try again later.`);
1964
+ broadcastSync({ type: 'message_created', conversationId, message: errorMessage, timestamp: Date.now() });
1965
+ broadcastSync({ type: 'streaming_complete', sessionId, conversationId, interrupted: true, timestamp: Date.now() });
1966
+ return;
1967
+ }
1968
+ rateLimitState.set(conversationId, {
1969
+ retryAt: Date.now() + (retryAfterSec * 1000),
1970
+ cooldownMs: retryAfterSec * 1000,
1971
+ retryCount: existingRetryCount + 1,
1973
1972
  isStreamDetected: true
1974
1973
  });
1975
1974
 
@@ -2073,27 +2072,38 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
2073
2072
  } catch (e) {}
2074
2073
  }
2075
2074
 
2076
- rateLimitState.set(conversationId, {
2077
- retryAt: Date.now() + (retryAfterSec * 1000),
2078
- cooldownMs: retryAfterSec * 1000,
2079
- retryCount: 0,
2075
+ const existingRetryCount2 = rateLimitState.get(conversationId)?.retryCount || 0;
2076
+ if (existingRetryCount2 >= 3) {
2077
+ debugLog(`[rate-limit] Conv ${conversationId} result rate limit hit ${existingRetryCount2 + 1} times, giving up`);
2078
+ batcher.drain();
2079
+ activeExecutions.delete(conversationId);
2080
+ queries.setIsStreaming(conversationId, false);
2081
+ const errorMessage = queries.createMessage(conversationId, 'assistant', `Error: Rate limit exceeded after ${existingRetryCount2 + 1} attempts. Please try again later.`);
2082
+ broadcastSync({ type: 'message_created', conversationId, message: errorMessage, timestamp: Date.now() });
2083
+ broadcastSync({ type: 'streaming_complete', sessionId, conversationId, interrupted: true, timestamp: Date.now() });
2084
+ return;
2085
+ }
2086
+ rateLimitState.set(conversationId, {
2087
+ retryAt: Date.now() + (retryAfterSec * 1000),
2088
+ cooldownMs: retryAfterSec * 1000,
2089
+ retryCount: existingRetryCount2 + 1,
2080
2090
  isStreamDetected: true
2081
2091
  });
2082
-
2092
+
2083
2093
  broadcastSync({
2084
2094
  type: 'rate_limit_hit',
2085
2095
  sessionId,
2086
2096
  conversationId,
2087
2097
  retryAfterMs: retryAfterSec * 1000,
2088
2098
  retryAt: Date.now() + (retryAfterSec * 1000),
2089
- retryCount: 1,
2099
+ retryCount: existingRetryCount2 + 1,
2090
2100
  timestamp: Date.now()
2091
2101
  });
2092
-
2102
+
2093
2103
  batcher.drain();
2094
2104
  activeExecutions.delete(conversationId);
2095
2105
  queries.setIsStreaming(conversationId, false);
2096
-
2106
+
2097
2107
  setTimeout(() => {
2098
2108
  rateLimitState.delete(conversationId);
2099
2109
  broadcastSync({
@@ -2103,7 +2113,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
2103
2113
  });
2104
2114
  scheduleRetry(conversationId, messageId, content, agentId, model, subAgent);
2105
2115
  }, retryAfterSec * 1000);
2106
-
2116
+
2107
2117
  return;
2108
2118
  }
2109
2119
 
@@ -2938,10 +2948,12 @@ function killActiveExecutions() {
2938
2948
 
2939
2949
  process.on('SIGTERM', () => {
2940
2950
  console.log('[SIGNAL] SIGTERM received - graceful shutdown');
2941
- if (!watch) killActiveExecutions();
2951
+ killActiveExecutions();
2952
+ if (jsonlWatcher) try { jsonlWatcher.stop(); } catch (_) {}
2942
2953
  try { pm2Manager.disconnect(); } catch (_) {}
2943
2954
  stopACPTools().catch(() => {}).finally(() => {
2944
2955
  try { wss.close(() => server.close(() => process.exit(0))); } catch (_) { process.exit(0); }
2956
+ setTimeout(() => process.exit(1), 5000);
2945
2957
  });
2946
2958
  });
2947
2959