Merge remote-tracking branch 'origin/main' into handoff-compaction

This commit is contained in:
Tak hoffman 2026-01-27 20:50:58 -06:00
commit 2c7b58d35f
5 changed files with 38 additions and 3 deletions

View File

@ -79,6 +79,7 @@ Status: beta.
- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.
- Agents: guard channel tool listActions to avoid plugin crashes. (#2859) Thanks @mbelinky.
- Providers: update MiniMax API endpoint and compatibility mode. (#3064) Thanks @hlbbbbbbb.
- Telegram: treat more network errors as recoverable in polling. (#3013) Thanks @ryancontent.
- Gateway: suppress AbortError and transient network errors in unhandled rejections. (#2451) Thanks @Glucksberg.
- TTS: keep /tts status replies on text-only commands and avoid duplicate block-stream audio. (#2451) Thanks @Glucksberg.
- Security: pin npm overrides to keep tar@7.5.4 for install toolchains.

View File

@ -322,7 +322,7 @@ export const registerTelegramNativeCommands = ({
];
if (allCommands.length > 0) {
void withTelegramApiErrorLogging({
withTelegramApiErrorLogging({
operation: "setMyCommands",
runtime,
fn: () => bot.api.setMyCommands(allCommands),
@ -576,7 +576,7 @@ export const registerTelegramNativeCommands = ({
}
}
} else if (nativeDisabledExplicit) {
void withTelegramApiErrorLogging({
withTelegramApiErrorLogging({
operation: "setMyCommands",
runtime,
fn: () => bot.api.setMyCommands([]),

View File

@ -74,6 +74,23 @@ const isGetUpdatesConflict = (err: unknown) => {
return haystack.includes("getupdates");
};
const NETWORK_ERROR_SNIPPETS = [
"fetch failed",
"network",
"timeout",
"socket",
"econnreset",
"econnrefused",
"undici",
];
const isNetworkRelatedError = (err: unknown) => {
if (!err) return false;
const message = formatErrorMessage(err).toLowerCase();
if (!message) return false;
return NETWORK_ERROR_SNIPPETS.some((snippet) => message.includes(snippet));
};
export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
const cfg = opts.config ?? loadConfig();
const account = resolveTelegramAccount({
@ -158,7 +175,8 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
}
const isConflict = isGetUpdatesConflict(err);
const isRecoverable = isRecoverableTelegramNetworkError(err, { context: "polling" });
if (!isConflict && !isRecoverable) {
const isNetworkError = isNetworkRelatedError(err);
if (!isConflict && !isRecoverable && !isNetworkError) {
throw err;
}
restartAttempts += 1;

View File

@ -8,6 +8,13 @@ describe("isRecoverableTelegramNetworkError", () => {
expect(isRecoverableTelegramNetworkError(err)).toBe(true);
});
it("detects additional recoverable error codes", () => {
const aborted = Object.assign(new Error("aborted"), { code: "ECONNABORTED" });
const network = Object.assign(new Error("network"), { code: "ERR_NETWORK" });
expect(isRecoverableTelegramNetworkError(aborted)).toBe(true);
expect(isRecoverableTelegramNetworkError(network)).toBe(true);
});
it("detects AbortError names", () => {
const err = Object.assign(new Error("The operation was aborted"), { name: "AbortError" });
expect(isRecoverableTelegramNetworkError(err)).toBe(true);
@ -19,6 +26,11 @@ describe("isRecoverableTelegramNetworkError", () => {
expect(isRecoverableTelegramNetworkError(err)).toBe(true);
});
it("detects expanded message patterns", () => {
expect(isRecoverableTelegramNetworkError(new Error("TypeError: fetch failed"))).toBe(true);
expect(isRecoverableTelegramNetworkError(new Error("Undici: socket failure"))).toBe(true);
});
it("skips message matches for send context", () => {
const err = new TypeError("fetch failed");
expect(isRecoverableTelegramNetworkError(err, { context: "send" })).toBe(false);

View File

@ -15,6 +15,8 @@ const RECOVERABLE_ERROR_CODES = new Set([
"UND_ERR_BODY_TIMEOUT",
"UND_ERR_SOCKET",
"UND_ERR_ABORTED",
"ECONNABORTED",
"ERR_NETWORK",
]);
const RECOVERABLE_ERROR_NAMES = new Set([
@ -27,6 +29,8 @@ const RECOVERABLE_ERROR_NAMES = new Set([
const RECOVERABLE_MESSAGE_SNIPPETS = [
"fetch failed",
"typeerror: fetch failed",
"undici",
"network error",
"network request",
"client network socket disconnected",