agentgui 1.0.186 → 1.0.188
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/lib/claude-runner.js +25 -2
- package/package.json +1 -1
- package/server.js +61 -7
package/lib/claude-runner.js
CHANGED
|
@@ -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)
|
|
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
package/server.js
CHANGED
|
@@ -352,7 +352,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
352
352
|
sendJSON(req, res, 201, { message, session, idempotencyKey });
|
|
353
353
|
|
|
354
354
|
processMessageWithStreaming(conversationId, message.id, session.id, body.content, agentId)
|
|
355
|
-
.catch(err =>
|
|
355
|
+
.catch(err => {
|
|
356
|
+
console.error(`[messages] Uncaught error for conv ${conversationId}:`, err.message);
|
|
357
|
+
debugLog(`[messages] Uncaught error: ${err.message}`);
|
|
358
|
+
});
|
|
356
359
|
return;
|
|
357
360
|
}
|
|
358
361
|
}
|
|
@@ -837,6 +840,15 @@ function createChunkBatcher() {
|
|
|
837
840
|
|
|
838
841
|
async function processMessageWithStreaming(conversationId, messageId, sessionId, content, agentId) {
|
|
839
842
|
const startTime = Date.now();
|
|
843
|
+
|
|
844
|
+
const conv = queries.getConversation(conversationId);
|
|
845
|
+
if (!conv) {
|
|
846
|
+
console.error(`[stream] Conversation ${conversationId} not found, aborting`);
|
|
847
|
+
queries.updateSession(sessionId, { status: 'error', error: 'Conversation not found' });
|
|
848
|
+
queries.setIsStreaming(conversationId, false);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
840
852
|
activeExecutions.set(conversationId, { pid: null, startTime, sessionId, lastActivity: startTime });
|
|
841
853
|
queries.setIsStreaming(conversationId, true);
|
|
842
854
|
queries.updateSession(sessionId, { status: 'active' });
|
|
@@ -845,7 +857,6 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
845
857
|
try {
|
|
846
858
|
debugLog(`[stream] Starting: conversationId=${conversationId}, sessionId=${sessionId}`);
|
|
847
859
|
|
|
848
|
-
const conv = queries.getConversation(conversationId);
|
|
849
860
|
const cwd = conv?.workingDirectory || STARTUP_CWD;
|
|
850
861
|
const resumeSessionId = conv?.claudeSessionId || null;
|
|
851
862
|
|
|
@@ -1014,10 +1025,35 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1014
1025
|
});
|
|
1015
1026
|
|
|
1016
1027
|
if (isRateLimit) {
|
|
1028
|
+
const existingState = rateLimitState.get(conversationId) || {};
|
|
1029
|
+
const retryCount = (existingState.retryCount || 0) + 1;
|
|
1030
|
+
const maxRateLimitRetries = 3;
|
|
1031
|
+
|
|
1032
|
+
if (retryCount > maxRateLimitRetries) {
|
|
1033
|
+
debugLog(`[rate-limit] Conv ${conversationId} hit rate limit ${retryCount} times, giving up`);
|
|
1034
|
+
broadcastSync({
|
|
1035
|
+
type: 'streaming_error',
|
|
1036
|
+
sessionId,
|
|
1037
|
+
conversationId,
|
|
1038
|
+
error: `Rate limit exceeded after ${retryCount} attempts. Please try again later.`,
|
|
1039
|
+
recoverable: false,
|
|
1040
|
+
timestamp: Date.now()
|
|
1041
|
+
});
|
|
1042
|
+
const errorMessage = queries.createMessage(conversationId, 'assistant', `Error: Rate limit exceeded after ${retryCount} attempts. Please try again later.`);
|
|
1043
|
+
broadcastSync({
|
|
1044
|
+
type: 'message_created',
|
|
1045
|
+
conversationId,
|
|
1046
|
+
message: errorMessage,
|
|
1047
|
+
timestamp: Date.now()
|
|
1048
|
+
});
|
|
1049
|
+
queries.setIsStreaming(conversationId, false);
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1017
1053
|
const cooldownMs = (error.retryAfterSec || 60) * 1000;
|
|
1018
1054
|
const retryAt = Date.now() + cooldownMs;
|
|
1019
|
-
rateLimitState.set(conversationId, { retryAt, cooldownMs });
|
|
1020
|
-
debugLog(`[rate-limit] Conv ${conversationId} hit rate limit, retry in ${cooldownMs}ms`);
|
|
1055
|
+
rateLimitState.set(conversationId, { retryAt, cooldownMs, retryCount });
|
|
1056
|
+
debugLog(`[rate-limit] Conv ${conversationId} hit rate limit (attempt ${retryCount}/${maxRateLimitRetries}), retry in ${cooldownMs}ms`);
|
|
1021
1057
|
|
|
1022
1058
|
broadcastSync({
|
|
1023
1059
|
type: 'rate_limit_hit',
|
|
@@ -1025,14 +1061,18 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1025
1061
|
conversationId,
|
|
1026
1062
|
retryAfterMs: cooldownMs,
|
|
1027
1063
|
retryAt,
|
|
1064
|
+
retryCount,
|
|
1028
1065
|
timestamp: Date.now()
|
|
1029
1066
|
});
|
|
1030
1067
|
|
|
1031
1068
|
batcher.drain();
|
|
1032
1069
|
|
|
1070
|
+
debugLog(`[rate-limit] Scheduling retry for conv ${conversationId} in ${cooldownMs}ms (attempt ${retryCount + 1})`);
|
|
1071
|
+
|
|
1033
1072
|
setTimeout(() => {
|
|
1073
|
+
debugLog(`[rate-limit] Timeout fired for conv ${conversationId}, calling scheduleRetry`);
|
|
1034
1074
|
rateLimitState.delete(conversationId);
|
|
1035
|
-
debugLog(`[rate-limit] Conv ${conversationId} cooldown expired, restarting`);
|
|
1075
|
+
debugLog(`[rate-limit] Conv ${conversationId} cooldown expired, restarting (attempt ${retryCount + 1})`);
|
|
1036
1076
|
broadcastSync({
|
|
1037
1077
|
type: 'rate_limit_clear',
|
|
1038
1078
|
conversationId,
|
|
@@ -1062,17 +1102,27 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1062
1102
|
} finally {
|
|
1063
1103
|
batcher.drain();
|
|
1064
1104
|
activeExecutions.delete(conversationId);
|
|
1065
|
-
queries.setIsStreaming(conversationId, false);
|
|
1066
1105
|
if (!rateLimitState.has(conversationId)) {
|
|
1106
|
+
queries.setIsStreaming(conversationId, false);
|
|
1067
1107
|
drainMessageQueue(conversationId);
|
|
1068
1108
|
}
|
|
1069
1109
|
}
|
|
1070
1110
|
}
|
|
1071
1111
|
|
|
1072
1112
|
function scheduleRetry(conversationId, messageId, content, agentId) {
|
|
1113
|
+
debugLog(`[rate-limit] scheduleRetry called for conv ${conversationId}, messageId=${messageId}`);
|
|
1114
|
+
|
|
1115
|
+
if (!content) {
|
|
1116
|
+
const conv = queries.getConversation(conversationId);
|
|
1117
|
+
const lastMsg = queries.getLastUserMessage(conversationId);
|
|
1118
|
+
content = lastMsg?.content || 'continue';
|
|
1119
|
+
debugLog(`[rate-limit] Recovered content from last message: ${content?.substring?.(0, 50)}...`);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1073
1122
|
const newSession = queries.createSession(conversationId);
|
|
1074
1123
|
queries.createEvent('session.created', { messageId, sessionId: newSession.id, retryReason: 'rate_limit' }, conversationId, newSession.id);
|
|
1075
1124
|
|
|
1125
|
+
debugLog(`[rate-limit] Broadcasting streaming_start for retry session ${newSession.id}`);
|
|
1076
1126
|
broadcastSync({
|
|
1077
1127
|
type: 'streaming_start',
|
|
1078
1128
|
sessionId: newSession.id,
|
|
@@ -1082,8 +1132,12 @@ function scheduleRetry(conversationId, messageId, content, agentId) {
|
|
|
1082
1132
|
timestamp: Date.now()
|
|
1083
1133
|
});
|
|
1084
1134
|
|
|
1135
|
+
debugLog(`[rate-limit] Calling processMessageWithStreaming for retry`);
|
|
1085
1136
|
processMessageWithStreaming(conversationId, messageId, newSession.id, content, agentId)
|
|
1086
|
-
.catch(err =>
|
|
1137
|
+
.catch(err => {
|
|
1138
|
+
debugLog(`[rate-limit] Retry failed: ${err.message}`);
|
|
1139
|
+
console.error(`[rate-limit] Retry error for conv ${conversationId}:`, err);
|
|
1140
|
+
});
|
|
1087
1141
|
}
|
|
1088
1142
|
|
|
1089
1143
|
function drainMessageQueue(conversationId) {
|