agentgui 1.0.185 → 1.0.187

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.
@@ -107,11 +107,34 @@ class AgentRunner {
107
107
  const errorText = chunk.toString();
108
108
  console.error(`[${this.id}] stderr:`, errorText);
109
109
 
110
- const rateLimitMatch = errorText.match(/rate.?limit|429|too many requests|overloaded|throttl/i);
110
+ const rateLimitMatch = errorText.match(/rate.?limit|429|too many requests|overloaded|throttl|hit your limit/i);
111
111
  if (rateLimitMatch) {
112
112
  rateLimited = true;
113
113
  const retryMatch = errorText.match(/retry.?after[:\s]+(\d+)/i);
114
- if (retryMatch) retryAfterSec = parseInt(retryMatch[1], 10) || 60;
114
+ if (retryMatch) {
115
+ retryAfterSec = parseInt(retryMatch[1], 10) || 60;
116
+ } else {
117
+ const resetTimeMatch = errorText.match(/resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*\(?(UTC|[A-Z]{2,4})\)?/i);
118
+ if (resetTimeMatch) {
119
+ let hours = parseInt(resetTimeMatch[1], 10);
120
+ const minutes = resetTimeMatch[2] ? parseInt(resetTimeMatch[2], 10) : 0;
121
+ const period = resetTimeMatch[3]?.toLowerCase();
122
+ const tz = resetTimeMatch[4]?.toUpperCase() || 'UTC';
123
+
124
+ if (period === 'pm' && hours !== 12) hours += 12;
125
+ if (period === 'am' && hours === 12) hours = 0;
126
+
127
+ const now = new Date();
128
+ const resetTime = new Date(now);
129
+ resetTime.setUTCHours(hours, minutes, 0, 0);
130
+
131
+ if (resetTime <= now) {
132
+ resetTime.setUTCDate(resetTime.getUTCDate() + 1);
133
+ }
134
+
135
+ retryAfterSec = Math.max(60, Math.ceil((resetTime.getTime() - now.getTime()) / 1000));
136
+ }
137
+ }
115
138
  }
116
139
 
117
140
  if (onError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.185",
3
+ "version": "1.0.187",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -1014,10 +1014,34 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
1014
1014
  });
1015
1015
 
1016
1016
  if (isRateLimit) {
1017
+ const existingState = rateLimitState.get(conversationId) || {};
1018
+ const retryCount = (existingState.retryCount || 0) + 1;
1019
+ const maxRateLimitRetries = 3;
1020
+
1021
+ if (retryCount > maxRateLimitRetries) {
1022
+ debugLog(`[rate-limit] Conv ${conversationId} hit rate limit ${retryCount} times, giving up`);
1023
+ broadcastSync({
1024
+ type: 'streaming_error',
1025
+ sessionId,
1026
+ conversationId,
1027
+ error: `Rate limit exceeded after ${retryCount} attempts. Please try again later.`,
1028
+ recoverable: false,
1029
+ timestamp: Date.now()
1030
+ });
1031
+ const errorMessage = queries.createMessage(conversationId, 'assistant', `Error: Rate limit exceeded after ${retryCount} attempts. Please try again later.`);
1032
+ broadcastSync({
1033
+ type: 'message_created',
1034
+ conversationId,
1035
+ message: errorMessage,
1036
+ timestamp: Date.now()
1037
+ });
1038
+ return;
1039
+ }
1040
+
1017
1041
  const cooldownMs = (error.retryAfterSec || 60) * 1000;
1018
1042
  const retryAt = Date.now() + cooldownMs;
1019
- rateLimitState.set(conversationId, { retryAt, cooldownMs });
1020
- debugLog(`[rate-limit] Conv ${conversationId} hit rate limit, retry in ${cooldownMs}ms`);
1043
+ rateLimitState.set(conversationId, { retryAt, cooldownMs, retryCount });
1044
+ debugLog(`[rate-limit] Conv ${conversationId} hit rate limit (attempt ${retryCount}/${maxRateLimitRetries}), retry in ${cooldownMs}ms`);
1021
1045
 
1022
1046
  broadcastSync({
1023
1047
  type: 'rate_limit_hit',
@@ -1025,6 +1049,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
1025
1049
  conversationId,
1026
1050
  retryAfterMs: cooldownMs,
1027
1051
  retryAt,
1052
+ retryCount,
1028
1053
  timestamp: Date.now()
1029
1054
  });
1030
1055
 
@@ -1032,7 +1057,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
1032
1057
 
1033
1058
  setTimeout(() => {
1034
1059
  rateLimitState.delete(conversationId);
1035
- debugLog(`[rate-limit] Conv ${conversationId} cooldown expired, restarting`);
1060
+ debugLog(`[rate-limit] Conv ${conversationId} cooldown expired, restarting (attempt ${retryCount + 1})`);
1036
1061
  broadcastSync({
1037
1062
  type: 'rate_limit_clear',
1038
1063
  conversationId,
@@ -1172,10 +1197,18 @@ wss.on('connection', (ws, req) => {
1172
1197
  timestamp: Date.now()
1173
1198
  }));
1174
1199
  } else if (data.type === 'unsubscribe') {
1175
- ws.subscriptions.delete(data.sessionId);
1176
- const idx = subscriptionIndex.get(data.sessionId);
1177
- if (idx) { idx.delete(ws); if (idx.size === 0) subscriptionIndex.delete(data.sessionId); }
1178
- debugLog(`[WebSocket] Client ${ws.clientId} unsubscribed from ${data.sessionId}`);
1200
+ if (data.sessionId) {
1201
+ ws.subscriptions.delete(data.sessionId);
1202
+ const idx = subscriptionIndex.get(data.sessionId);
1203
+ if (idx) { idx.delete(ws); if (idx.size === 0) subscriptionIndex.delete(data.sessionId); }
1204
+ }
1205
+ if (data.conversationId) {
1206
+ const key = `conv-${data.conversationId}`;
1207
+ ws.subscriptions.delete(key);
1208
+ const idx = subscriptionIndex.get(key);
1209
+ if (idx) { idx.delete(ws); if (idx.size === 0) subscriptionIndex.delete(key); }
1210
+ }
1211
+ debugLog(`[WebSocket] Client ${ws.clientId} unsubscribed from ${data.sessionId || data.conversationId}`);
1179
1212
  } else if (data.type === 'get_subscriptions') {
1180
1213
  ws.send(JSON.stringify({
1181
1214
  type: 'subscriptions',
@@ -1335,7 +1335,11 @@ class AgentGUIClient {
1335
1335
  try {
1336
1336
  this.cacheCurrentConversation();
1337
1337
  this.stopChunkPolling();
1338
- if (this.state.currentConversation?.id !== conversationId) {
1338
+ var prevId = this.state.currentConversation?.id;
1339
+ if (prevId && prevId !== conversationId) {
1340
+ if (this.wsManager.isConnected && !this.state.streamingConversations.has(prevId)) {
1341
+ this.wsManager.sendMessage({ type: 'unsubscribe', conversationId: prevId });
1342
+ }
1339
1343
  this.state.currentSession = null;
1340
1344
  }
1341
1345
 
@@ -585,9 +585,11 @@
585
585
  }
586
586
  if (!voiceActive) return;
587
587
  if (data.type === 'streaming_progress' && data.block) {
588
+ if (data.conversationId && data.conversationId !== currentConversationId) return;
588
589
  handleVoiceBlock(data.block, true);
589
590
  }
590
591
  if (data.type === 'streaming_start') {
592
+ if (data.conversationId && data.conversationId !== currentConversationId) return;
591
593
  spokenChunks = new Set();
592
594
  }
593
595
  });