mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
fix(telegram): bound offset confirmation timeout (#50368) (thanks @boticlaw)
This commit is contained in:
@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Telegram/polling: bound the persisted-offset confirmation `getUpdates` probe with a client-side timeout so a zombie socket cannot hang polling recovery before the runner watchdog starts. (#50368) Thanks @boticlaw.
|
||||
- Plugins/memory: preserve the active memory capability when read-only snapshot plugin loads run, so status and provider discovery paths no longer wipe memory public artifacts. (#69219) Thanks @zeroaltitude.
|
||||
- Plugins: keep only the highest-precedence manifest when distinct discovered plugins share an id, so lower-precedence global or workspace duplicates no longer load beside bundled or config-selected plugins. (#41626) Thanks @Tortes.
|
||||
- fix(security): block MINIMAX_API_HOST workspace env injection and remove env-driven URL routing [AI-assisted]. (#67300) Thanks @pgondhi987.
|
||||
@@ -27,6 +28,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/websocket broadcasts: require `operator.read` (or higher) for chat, agent, and tool-result event frames so pairing-scoped and node-role sessions no longer passively receive session chat content, and scope-gate unknown broadcast events by default. Plugin-defined `plugin.*` broadcasts are scoped to operator.write/admin, and status/transport events (`heartbeat`, `presence`, `tick`, etc.) remain unrestricted. Per-client sequence numbers preserve per-connection monotonicity. (#69373) Thanks @eleqtrizit.
|
||||
- Agents/compaction: always reload embedded Pi resources through an explicit loader and reapply reserve-token overrides so runs without extension factories no longer silently lose compaction settings before session start. (#67146) Thanks @ly85206559.
|
||||
- Memory-core/dreaming: normalize sweep timestamps and reuse hashed narrative session keys for fallback cleanup so Dreaming narrative sub-sessions stop leaking. (#67023) Thanks @chiyouYCH.
|
||||
|
||||
## 2026.4.20
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -743,7 +743,10 @@ describe("monitorTelegramProvider (grammY)", () => {
|
||||
persistedOffset: 549076203,
|
||||
});
|
||||
|
||||
expect(api.getUpdates).toHaveBeenCalledWith({ offset: 549076204, limit: 1, timeout: 0 });
|
||||
expect(api.getUpdates).toHaveBeenCalledWith(
|
||||
{ offset: 549076204, limit: 1, timeout: 0 },
|
||||
expect.any(AbortSignal),
|
||||
);
|
||||
expect(order).toEqual(["deleteWebhook", "getUpdates", "run"]);
|
||||
});
|
||||
|
||||
|
||||
@@ -278,6 +278,47 @@ describe("TelegramPollingSession", () => {
|
||||
expect(sleepWithAbortMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("bounds the persisted offset confirmation getUpdates call", async () => {
|
||||
const abort = new AbortController();
|
||||
const timeoutSignal = new AbortController().signal;
|
||||
const timeoutSpy = vi.spyOn(AbortSignal, "timeout").mockReturnValue(timeoutSignal);
|
||||
const bot = makeBot();
|
||||
createTelegramBotMock.mockReturnValueOnce(bot);
|
||||
runMock.mockReturnValueOnce({
|
||||
task: async () => {
|
||||
abort.abort();
|
||||
},
|
||||
stop: vi.fn(async () => undefined),
|
||||
isRunning: () => false,
|
||||
});
|
||||
|
||||
const session = new TelegramPollingSession({
|
||||
token: "tok",
|
||||
config: {},
|
||||
accountId: "default",
|
||||
runtime: undefined,
|
||||
proxyFetch: undefined,
|
||||
abortSignal: abort.signal,
|
||||
runnerOptions: {},
|
||||
getLastUpdateId: () => 41,
|
||||
persistUpdateId: async () => undefined,
|
||||
log: () => undefined,
|
||||
telegramTransport: undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
await session.runUntilAbort();
|
||||
|
||||
expect(timeoutSpy).toHaveBeenCalledWith(10_000);
|
||||
expect(bot.api.getUpdates).toHaveBeenCalledWith(
|
||||
{ offset: 42, limit: 1, timeout: 0 },
|
||||
timeoutSignal,
|
||||
);
|
||||
} finally {
|
||||
timeoutSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("forces a restart when polling stalls without getUpdates activity", async () => {
|
||||
const abort = new AbortController();
|
||||
const botStop = vi.fn(async () => undefined);
|
||||
|
||||
@@ -25,6 +25,10 @@ const TELEGRAM_POLL_RESTART_POLICY = {
|
||||
const POLL_STALL_THRESHOLD_MS = 90_000;
|
||||
const POLL_WATCHDOG_INTERVAL_MS = 30_000;
|
||||
const POLL_STOP_GRACE_MS = 15_000;
|
||||
const CONFIRM_PERSISTED_OFFSET_TIMEOUT_MS = 10_000;
|
||||
|
||||
type TelegramBot = ReturnType<typeof createTelegramBot>;
|
||||
type TelegramApiAbortSignal = Parameters<TelegramBot["api"]["getUpdates"]>[1];
|
||||
|
||||
const waitForGracefulStop = async (stop: () => Promise<void>) => {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
@@ -43,7 +47,8 @@ const waitForGracefulStop = async (stop: () => Promise<void>) => {
|
||||
}
|
||||
};
|
||||
|
||||
type TelegramBot = ReturnType<typeof createTelegramBot>;
|
||||
const telegramApiTimeoutSignal = (timeoutMs: number): TelegramApiAbortSignal =>
|
||||
AbortSignal.timeout(timeoutMs) as unknown as TelegramApiAbortSignal;
|
||||
|
||||
type TelegramPollingSessionOpts = {
|
||||
token: string;
|
||||
@@ -212,7 +217,7 @@ export class TelegramPollingSession {
|
||||
try {
|
||||
await bot.api.getUpdates(
|
||||
{ offset: lastUpdateId + 1, limit: 1, timeout: 0 },
|
||||
{ signal: AbortSignal.timeout(10000) },
|
||||
telegramApiTimeoutSignal(CONFIRM_PERSISTED_OFFSET_TIMEOUT_MS),
|
||||
);
|
||||
} catch {
|
||||
// Non-fatal: runner middleware still skips duplicates via shouldSkipUpdate.
|
||||
|
||||
Reference in New Issue
Block a user