mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:20:45 +00:00
fix(telegram): warn on stale polling status
This commit is contained in:
@@ -2,6 +2,7 @@ import type {
|
||||
ChannelAccountSnapshot,
|
||||
ChannelStatusIssue,
|
||||
} from "openclaw/plugin-sdk/channel-contract";
|
||||
import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
|
||||
import {
|
||||
appendMatchMetadata,
|
||||
asString,
|
||||
@@ -9,10 +10,19 @@ import {
|
||||
resolveEnabledConfiguredAccountId,
|
||||
} from "openclaw/plugin-sdk/status-helpers";
|
||||
|
||||
const TELEGRAM_POLLING_CONNECT_GRACE_MS = 120_000;
|
||||
const TELEGRAM_POLLING_STALE_TRANSPORT_MS = 30 * 60_000;
|
||||
|
||||
type TelegramAccountStatus = {
|
||||
accountId?: unknown;
|
||||
enabled?: unknown;
|
||||
configured?: unknown;
|
||||
running?: unknown;
|
||||
connected?: unknown;
|
||||
mode?: unknown;
|
||||
lastStartAt?: unknown;
|
||||
lastTransportActivityAt?: unknown;
|
||||
lastError?: unknown;
|
||||
allowUnmentionedGroups?: unknown;
|
||||
audit?: unknown;
|
||||
};
|
||||
@@ -38,11 +48,82 @@ function readTelegramAccountStatus(value: ChannelAccountSnapshot): TelegramAccou
|
||||
accountId: value.accountId,
|
||||
enabled: value.enabled,
|
||||
configured: value.configured,
|
||||
running: value.running,
|
||||
connected: value.connected,
|
||||
mode: value.mode,
|
||||
lastStartAt: value.lastStartAt,
|
||||
lastTransportActivityAt: value.lastTransportActivityAt,
|
||||
lastError: value.lastError,
|
||||
allowUnmentionedGroups: value.allowUnmentionedGroups,
|
||||
audit: value.audit,
|
||||
};
|
||||
}
|
||||
|
||||
function asFiniteNumber(value: unknown): number | null {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function appendTelegramRuntimeError(message: string, lastError: unknown): string {
|
||||
const error = asString(lastError);
|
||||
return error ? `${message}: ${error}` : message;
|
||||
}
|
||||
|
||||
function collectTelegramPollingRuntimeIssues(params: {
|
||||
account: TelegramAccountStatus;
|
||||
accountId: string;
|
||||
issues: ChannelStatusIssue[];
|
||||
now: number;
|
||||
}) {
|
||||
const { account, accountId, issues, now } = params;
|
||||
if (account.running !== true || asString(account.mode) !== "polling") {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastStartAt = asFiniteNumber(account.lastStartAt);
|
||||
const lastTransportActivityAt = asFiniteNumber(account.lastTransportActivityAt);
|
||||
const fix = `Run: ${formatCliCommand("openclaw channels status --probe")} (or restart the gateway). Check the bot token, proxy/network settings, and logs if it persists.`;
|
||||
|
||||
if (account.connected === false) {
|
||||
const withinStartupGrace =
|
||||
lastStartAt != null && now - lastStartAt < TELEGRAM_POLLING_CONNECT_GRACE_MS;
|
||||
if (!withinStartupGrace) {
|
||||
issues.push({
|
||||
channel: "telegram",
|
||||
accountId,
|
||||
kind: "runtime",
|
||||
message: appendTelegramRuntimeError(
|
||||
"Telegram polling is running but has not completed a successful getUpdates call since startup",
|
||||
account.lastError,
|
||||
),
|
||||
fix,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (account.connected === true && lastTransportActivityAt != null) {
|
||||
if (lastStartAt != null && lastTransportActivityAt < lastStartAt) {
|
||||
const lifecycleAgeMs = Math.max(0, now - lastStartAt);
|
||||
if (lifecycleAgeMs <= TELEGRAM_POLLING_STALE_TRANSPORT_MS) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const ageMs = now - lastTransportActivityAt;
|
||||
if (ageMs > TELEGRAM_POLLING_STALE_TRANSPORT_MS) {
|
||||
issues.push({
|
||||
channel: "telegram",
|
||||
accountId,
|
||||
kind: "runtime",
|
||||
message: appendTelegramRuntimeError(
|
||||
`Telegram polling transport is stale (last successful getUpdates ${Math.max(0, Math.floor(ageMs / 60_000))}m ago)`,
|
||||
account.lastError,
|
||||
),
|
||||
fix,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readTelegramGroupMembershipAuditSummary(
|
||||
value: unknown,
|
||||
): TelegramGroupMembershipAuditSummary {
|
||||
@@ -94,6 +175,13 @@ export function collectTelegramStatusIssues(
|
||||
continue;
|
||||
}
|
||||
|
||||
collectTelegramPollingRuntimeIssues({
|
||||
account,
|
||||
accountId,
|
||||
issues,
|
||||
now: Date.now(),
|
||||
});
|
||||
|
||||
if (account.allowUnmentionedGroups === true) {
|
||||
issues.push({
|
||||
channel: "telegram",
|
||||
|
||||
@@ -73,6 +73,105 @@ describe("collectTelegramStatusIssues", () => {
|
||||
expect(issues[0]?.message).toContain("channels.telegram.groups");
|
||||
});
|
||||
|
||||
it("reports polling runtime state that never completed getUpdates after startup grace", () => {
|
||||
const issues = collectTelegramStatusIssues([
|
||||
{
|
||||
accountId: "main",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: true,
|
||||
mode: "polling",
|
||||
connected: false,
|
||||
lastStartAt: Date.now() - 121_000,
|
||||
lastError: "network timeout",
|
||||
} as ChannelAccountSnapshot,
|
||||
]);
|
||||
|
||||
expect(issues).toEqual([
|
||||
expect.objectContaining({
|
||||
channel: "telegram",
|
||||
accountId: "main",
|
||||
kind: "runtime",
|
||||
message: expect.stringContaining("has not completed a successful getUpdates call"),
|
||||
}),
|
||||
]);
|
||||
expect(issues[0]?.message).toContain("network timeout");
|
||||
expect(issues[0]?.fix).toContain("channels status --probe");
|
||||
});
|
||||
|
||||
it("does not report polling startup before the connect grace expires", () => {
|
||||
const issues = collectTelegramStatusIssues([
|
||||
{
|
||||
accountId: "main",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: true,
|
||||
mode: "polling",
|
||||
connected: false,
|
||||
lastStartAt: Date.now() - 60_000,
|
||||
} as ChannelAccountSnapshot,
|
||||
]);
|
||||
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
|
||||
it("reports stale polling transport activity after successful getUpdates stops refreshing", () => {
|
||||
const issues = collectTelegramStatusIssues([
|
||||
{
|
||||
accountId: "main",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: true,
|
||||
mode: "polling",
|
||||
connected: true,
|
||||
lastStartAt: Date.now() - 60 * 60_000,
|
||||
lastTransportActivityAt: Date.now() - 31 * 60_000,
|
||||
} as ChannelAccountSnapshot,
|
||||
]);
|
||||
|
||||
expect(issues).toEqual([
|
||||
expect.objectContaining({
|
||||
channel: "telegram",
|
||||
accountId: "main",
|
||||
kind: "runtime",
|
||||
message: expect.stringContaining("polling transport is stale"),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not report inherited stale transport activity during a fresh polling lifecycle", () => {
|
||||
const issues = collectTelegramStatusIssues([
|
||||
{
|
||||
accountId: "main",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: true,
|
||||
mode: "polling",
|
||||
connected: true,
|
||||
lastStartAt: Date.now() - 60_000,
|
||||
lastTransportActivityAt: Date.now() - 2 * 60 * 60_000,
|
||||
} as ChannelAccountSnapshot,
|
||||
]);
|
||||
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not report webhook accounts with polling-only runtime diagnostics", () => {
|
||||
const issues = collectTelegramStatusIssues([
|
||||
{
|
||||
accountId: "main",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: true,
|
||||
mode: "webhook",
|
||||
connected: false,
|
||||
lastStartAt: Date.now() - 10 * 60_000,
|
||||
} as ChannelAccountSnapshot,
|
||||
]);
|
||||
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
|
||||
it("ignores accounts that are not both enabled and configured", () => {
|
||||
expect(
|
||||
collectTelegramStatusIssues([
|
||||
|
||||
Reference in New Issue
Block a user