appback-remoteagent 0.14.2 → 0.14.4
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/dist/bot.js
CHANGED
|
@@ -1087,10 +1087,14 @@ async function runWithPendingAnimation(botToken, chatId, task) {
|
|
|
1087
1087
|
const rendered = formatProviderTelegramChunks(progressChunks, parseMode);
|
|
1088
1088
|
const extra = rendered.parseMode ? { parse_mode: rendered.parseMode } : undefined;
|
|
1089
1089
|
for (const chunk of rendered.chunks) {
|
|
1090
|
-
await sendTelegramMessage(botToken, chatId, chunk, extra)
|
|
1090
|
+
await sendTelegramMessage(botToken, chatId, chunk, extra).catch((error) => {
|
|
1091
|
+
console.warn(`[telegram-progress-delivery] chat=${chatId} dropped progress message: ${formatTelegramDeliveryError(error)}`);
|
|
1092
|
+
});
|
|
1091
1093
|
}
|
|
1092
1094
|
if (normalized.documents.length > 0) {
|
|
1093
|
-
await sendTelegramDocuments(botToken, chatId, normalized.documents)
|
|
1095
|
+
await sendTelegramDocuments(botToken, chatId, normalized.documents).catch((error) => {
|
|
1096
|
+
console.warn(`[telegram-progress-delivery] chat=${chatId} dropped progress document(s): ${formatTelegramDeliveryError(error)}`);
|
|
1097
|
+
});
|
|
1094
1098
|
}
|
|
1095
1099
|
},
|
|
1096
1100
|
};
|
|
@@ -1146,6 +1150,7 @@ async function routeTelegramWorkLoop(bridge, botId, chatId, message, label, botM
|
|
|
1146
1150
|
let untaggedIntentRetryCount = 0;
|
|
1147
1151
|
let missingEvidenceRetryCount = 0;
|
|
1148
1152
|
let deliveredProgressCount = 0;
|
|
1153
|
+
let providerCompleted = false;
|
|
1149
1154
|
const ensureStillBound = async (phase) => {
|
|
1150
1155
|
if (!sessionId) {
|
|
1151
1156
|
return;
|
|
@@ -1238,6 +1243,7 @@ async function routeTelegramWorkLoop(bridge, botId, chatId, message, label, botM
|
|
|
1238
1243
|
if (currentSession) {
|
|
1239
1244
|
await memoryService.completeTask(currentSession.session, parsed.chunks.join("\n"));
|
|
1240
1245
|
}
|
|
1246
|
+
providerCompleted = true;
|
|
1241
1247
|
autoContinue.clear(botId, chatId, sessionId);
|
|
1242
1248
|
return parsed.chunks;
|
|
1243
1249
|
}
|
|
@@ -1314,7 +1320,12 @@ async function routeTelegramWorkLoop(bridge, botId, chatId, message, label, botM
|
|
|
1314
1320
|
}
|
|
1315
1321
|
finally {
|
|
1316
1322
|
if (currentSession) {
|
|
1317
|
-
|
|
1323
|
+
if (providerCompleted) {
|
|
1324
|
+
await botManagement.markProviderCompleted(botId, sessionId);
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
await botManagement.markProviderIdle(botId, sessionId);
|
|
1328
|
+
}
|
|
1318
1329
|
}
|
|
1319
1330
|
}
|
|
1320
1331
|
}
|
|
@@ -2226,23 +2237,38 @@ async function sendTelegramDocument(botToken, chatId, document) {
|
|
|
2226
2237
|
async function sendTelegramMessage(botToken, chatId, text, extra) {
|
|
2227
2238
|
const startedAt = Date.now();
|
|
2228
2239
|
try {
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2240
|
+
let lastError;
|
|
2241
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
2242
|
+
try {
|
|
2243
|
+
try {
|
|
2244
|
+
return await callTelegramApi(botToken, "sendMessage", {
|
|
2245
|
+
chat_id: String(chatId),
|
|
2246
|
+
text,
|
|
2247
|
+
parse_mode: extra?.parse_mode,
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
catch (error) {
|
|
2251
|
+
if (!extra?.parse_mode || !isTelegramEntityParseError(error)) {
|
|
2252
|
+
throw error;
|
|
2253
|
+
}
|
|
2254
|
+
console.warn(`[telegram-sendMessage-fallback] chat=${chatId} parseMode=${extra.parse_mode}: ${formatTelegramDeliveryError(error)}`);
|
|
2255
|
+
return await callTelegramApi(botToken, "sendMessage", {
|
|
2256
|
+
chat_id: String(chatId),
|
|
2257
|
+
text: stripTelegramHtml(text),
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
catch (error) {
|
|
2262
|
+
lastError = error;
|
|
2263
|
+
if (attempt >= 3 || !isRetryableTelegramDeliveryError(error)) {
|
|
2264
|
+
throw error;
|
|
2265
|
+
}
|
|
2266
|
+
const delayMs = attempt * 1500;
|
|
2267
|
+
console.warn(`[telegram-sendMessage-retry] chat=${chatId} attempt=${attempt}/3 delayMs=${delayMs}: ${formatTelegramDeliveryError(error)}`);
|
|
2268
|
+
await sleep(delayMs);
|
|
2239
2269
|
}
|
|
2240
|
-
console.warn(`[telegram-sendMessage-fallback] chat=${chatId} parseMode=${extra.parse_mode}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2241
|
-
return await callTelegramApi(botToken, "sendMessage", {
|
|
2242
|
-
chat_id: String(chatId),
|
|
2243
|
-
text: stripTelegramHtml(text),
|
|
2244
|
-
});
|
|
2245
2270
|
}
|
|
2271
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
2246
2272
|
}
|
|
2247
2273
|
finally {
|
|
2248
2274
|
const elapsedMs = Date.now() - startedAt;
|
|
@@ -2282,7 +2308,16 @@ async function callTelegramApi(botToken, method, params) {
|
|
|
2282
2308
|
args.push("--data-urlencode", `${key}=${value}`);
|
|
2283
2309
|
}
|
|
2284
2310
|
}
|
|
2285
|
-
|
|
2311
|
+
let stdout;
|
|
2312
|
+
let stderr;
|
|
2313
|
+
try {
|
|
2314
|
+
const result = await execFileAsync("curl", args);
|
|
2315
|
+
stdout = result.stdout;
|
|
2316
|
+
stderr = result.stderr;
|
|
2317
|
+
}
|
|
2318
|
+
catch (error) {
|
|
2319
|
+
throw new TelegramApiError(method, summarizeCurlTelegramError(error, method));
|
|
2320
|
+
}
|
|
2286
2321
|
if (stderr?.trim()) {
|
|
2287
2322
|
console.error(`curl stderr for ${method}: ${stderr.trim()}`);
|
|
2288
2323
|
}
|
|
@@ -2302,6 +2337,26 @@ class TelegramApiError extends Error {
|
|
|
2302
2337
|
this.name = "TelegramApiError";
|
|
2303
2338
|
}
|
|
2304
2339
|
}
|
|
2340
|
+
function summarizeCurlTelegramError(error, method) {
|
|
2341
|
+
if (!(error instanceof Error)) {
|
|
2342
|
+
return `Telegram API ${method} failed: ${String(error)}`;
|
|
2343
|
+
}
|
|
2344
|
+
const stderr = typeof error === "object" && error !== null && "stderr" in error
|
|
2345
|
+
? String(error.stderr ?? "").trim()
|
|
2346
|
+
: "";
|
|
2347
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
2348
|
+
? String(error.code ?? "")
|
|
2349
|
+
: "";
|
|
2350
|
+
const message = stderr || error.message;
|
|
2351
|
+
return [`Telegram API ${method} curl failed`, code ? `code=${code}` : undefined, message].filter(Boolean).join(": ");
|
|
2352
|
+
}
|
|
2353
|
+
function formatTelegramDeliveryError(error) {
|
|
2354
|
+
return error instanceof Error ? error.message : String(error);
|
|
2355
|
+
}
|
|
2356
|
+
function isRetryableTelegramDeliveryError(error) {
|
|
2357
|
+
const message = formatTelegramDeliveryError(error);
|
|
2358
|
+
return /timed out|timeout|Bad Gateway|502|503|504|ECONNRESET|connection reset|EAI_AGAIN|ENOTFOUND/i.test(message);
|
|
2359
|
+
}
|
|
2305
2360
|
function isTelegramForbiddenError(error) {
|
|
2306
2361
|
if (error instanceof GrammyError) {
|
|
2307
2362
|
return error.error_code === 403 || /^Forbidden:/i.test(error.description);
|
package/dist/index.js
CHANGED
|
@@ -236,11 +236,13 @@ async function pollTelegramBot(pollingBot, runtime, options) {
|
|
|
236
236
|
runtime.offset = orderedUpdates[orderedUpdates.length - 1].update_id + 1;
|
|
237
237
|
}
|
|
238
238
|
const receivedMessage = orderedUpdates.some(hasMessagePayload);
|
|
239
|
-
const
|
|
239
|
+
const lastUserMessageAt = receivedMessage ? new Date(now).toISOString() : options.state?.lastUserMessageAt;
|
|
240
|
+
const lastMessageAt = receivedMessage ? lastUserMessageAt : options.state?.lastMessageAt;
|
|
240
241
|
const nextPollAt = now + computePolicyPollIntervalMs(options.totalBots, receivedMessage ? 1 : options.botRank, {
|
|
241
242
|
...options.state,
|
|
242
243
|
botId,
|
|
243
244
|
consecutiveFailures: options.state?.consecutiveFailures ?? runtime.consecutiveFailures,
|
|
245
|
+
lastUserMessageAt,
|
|
244
246
|
lastMessageAt,
|
|
245
247
|
}, {
|
|
246
248
|
tieredPollingMinBots: config.telegramTieredPollingMinBots,
|
|
@@ -253,6 +255,7 @@ async function pollTelegramBot(pollingBot, runtime, options) {
|
|
|
253
255
|
username: pollingBot.botInfo.username,
|
|
254
256
|
lastPollAt: new Date(now).toISOString(),
|
|
255
257
|
lastUpdateAt: orderedUpdates.length > 0 ? new Date(now).toISOString() : undefined,
|
|
258
|
+
lastUserMessageAt,
|
|
256
259
|
lastMessageAt,
|
|
257
260
|
nextPollAt: new Date(nextPollAt).toISOString(),
|
|
258
261
|
consecutiveFailures: 0,
|
|
@@ -54,6 +54,11 @@ export class BotManagementService {
|
|
|
54
54
|
const pollingBotId = bot ? String(bot.id) : botId;
|
|
55
55
|
await this.pollingState.markIdle(pollingBotId, sessionId, bot?.username);
|
|
56
56
|
}
|
|
57
|
+
async markProviderCompleted(botId, sessionId) {
|
|
58
|
+
const bot = await this.findConfiguredBot(botId);
|
|
59
|
+
const pollingBotId = bot ? String(bot.id) : botId;
|
|
60
|
+
await this.pollingState.markCompleted(pollingBotId, sessionId, bot?.username);
|
|
61
|
+
}
|
|
57
62
|
async getPendingOperationNotice() {
|
|
58
63
|
const pending = await this.readPendingOperation();
|
|
59
64
|
if (!pending) {
|
|
@@ -70,6 +70,21 @@ export class BotPollingStateService {
|
|
|
70
70
|
state.bots[botId] = current;
|
|
71
71
|
await this.writeNow();
|
|
72
72
|
}
|
|
73
|
+
async markCompleted(botId, sessionId, username) {
|
|
74
|
+
await this.markIdle(botId, sessionId, username);
|
|
75
|
+
const state = await this.read();
|
|
76
|
+
const current = state.bots[botId] ?? {
|
|
77
|
+
botId,
|
|
78
|
+
username,
|
|
79
|
+
consecutiveFailures: 0,
|
|
80
|
+
};
|
|
81
|
+
if (username) {
|
|
82
|
+
current.username = username;
|
|
83
|
+
}
|
|
84
|
+
current.lastProviderCompletedAt = new Date().toISOString();
|
|
85
|
+
state.bots[botId] = current;
|
|
86
|
+
await this.writeNow();
|
|
87
|
+
}
|
|
73
88
|
async recordPoll(botId, patch) {
|
|
74
89
|
const state = await this.read();
|
|
75
90
|
const current = state.bots[botId] ?? {
|
|
@@ -2,7 +2,7 @@ export function computeRecentMessageRanks(botIds, states) {
|
|
|
2
2
|
const ranked = botIds
|
|
3
3
|
.map((botId) => ({
|
|
4
4
|
botId,
|
|
5
|
-
timestamp:
|
|
5
|
+
timestamp: latestTimestamp(states[botId]?.lastUserMessageAt ?? states[botId]?.lastMessageAt, states[botId]?.lastProviderCompletedAt),
|
|
6
6
|
}))
|
|
7
7
|
.sort((left, right) => right.timestamp - left.timestamp);
|
|
8
8
|
return new Map(ranked.map((entry, index) => [entry.botId, index + 1]));
|
|
@@ -29,3 +29,6 @@ function parseStateTime(value) {
|
|
|
29
29
|
const timestamp = Date.parse(value);
|
|
30
30
|
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
31
31
|
}
|
|
32
|
+
function latestTimestamp(...values) {
|
|
33
|
+
return Math.max(0, ...values.map((value) => parseStateTime(value) ?? 0));
|
|
34
|
+
}
|
|
@@ -14,6 +14,13 @@ When 5 or more bots are configured:
|
|
|
14
14
|
- the next 4 idle bots poll every 60 seconds
|
|
15
15
|
- all remaining idle bots poll every 180 seconds
|
|
16
16
|
|
|
17
|
+
Recent activity is updated only when:
|
|
18
|
+
|
|
19
|
+
- the operator sends a message to that bot
|
|
20
|
+
- the provider returns a completed result to that bot
|
|
21
|
+
|
|
22
|
+
Polling itself must not update recent activity. A bot should not become "recent" merely because RemoteAgent checked Telegram for updates.
|
|
23
|
+
|
|
17
24
|
When a bot has active provider work:
|
|
18
25
|
|
|
19
26
|
- that bot polls every 60 seconds
|