mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
refactor: move stale socket modes to channel status
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
10f07ebae3910cfe7639c54fb97ec4011c5f8be8a5444b86d23f075f9a49cc4c plugin-sdk-api-baseline.json
|
||||
fdc165b2d06f00d195e326c2d28176da5cdeb8f8b05df4ec28466d384d57a07b plugin-sdk-api-baseline.jsonl
|
||||
fc00be212cab9fa24cf625fd9afb8f6d0871509afcc42baa6653d3ef26a991d1 plugin-sdk-api-baseline.json
|
||||
efe8884ee3a296ae77b80f1485d17744397c5868c110b23eb5cf99ce2587a03f plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -760,6 +760,7 @@ export const telegramPlugin = createChatChannelPlugin({
|
||||
actions: telegramMessageActions,
|
||||
status: createComputedAccountStatusAdapter<ResolvedTelegramAccount, TelegramProbe>({
|
||||
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
|
||||
staleSocketHealthCheckModes: ["polling"],
|
||||
collectStatusIssues: collectTelegramStatusIssues,
|
||||
buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
|
||||
probeAccount: async ({ account, timeoutMs }) =>
|
||||
|
||||
@@ -173,6 +173,8 @@ export type ChannelGroupAdapter = {
|
||||
export type ChannelStatusAdapter<ResolvedAccount, Probe = unknown, Audit = unknown> = {
|
||||
defaultRuntime?: ChannelAccountSnapshot;
|
||||
skipStaleSocketHealthCheck?: boolean;
|
||||
/** Runtime `mode` values where `lastEventAt` can prove connected socket liveness. */
|
||||
staleSocketHealthCheckModes?: readonly string[];
|
||||
buildChannelSummary?: BivariantCallback<
|
||||
(params: {
|
||||
account: ResolvedAccount;
|
||||
|
||||
@@ -125,12 +125,14 @@ export function startChannelHealthMonitor(deps: ChannelHealthMonitorDeps): Chann
|
||||
if (channelManager.isManuallyStopped(channelId as ChannelId, accountId)) {
|
||||
continue;
|
||||
}
|
||||
const channelPluginStatus = getChannelPlugin(channelId)?.status;
|
||||
const healthPolicy: ChannelHealthPolicy = {
|
||||
channelId,
|
||||
now,
|
||||
staleEventThresholdMs: timing.staleEventThresholdMs,
|
||||
channelConnectGraceMs: timing.channelConnectGraceMs,
|
||||
skipStaleSocketCheck: getChannelPlugin(channelId)?.status?.skipStaleSocketHealthCheck,
|
||||
skipStaleSocketCheck: channelPluginStatus?.skipStaleSocketHealthCheck,
|
||||
staleSocketHealthCheckModes: channelPluginStatus?.staleSocketHealthCheckModes,
|
||||
};
|
||||
const health = evaluateChannelHealth(status, healthPolicy);
|
||||
if (health.healthy) {
|
||||
|
||||
@@ -136,7 +136,7 @@ describe("evaluateChannelHealth", () => {
|
||||
expect(evaluation).toEqual({ healthy: false, reason: "stale-socket" });
|
||||
});
|
||||
|
||||
it("flags stale sockets for telegram polling channels", () => {
|
||||
it("flags stale sockets for channels with an allowed health-check mode", () => {
|
||||
const evaluation = evaluateChannelHealth(
|
||||
{
|
||||
running: true,
|
||||
@@ -148,16 +148,17 @@ describe("evaluateChannelHealth", () => {
|
||||
mode: "polling",
|
||||
},
|
||||
{
|
||||
channelId: "telegram",
|
||||
channelId: "example",
|
||||
now: 100_000,
|
||||
channelConnectGraceMs: 10_000,
|
||||
staleEventThresholdMs: 30_000,
|
||||
staleSocketHealthCheckModes: ["polling"],
|
||||
},
|
||||
);
|
||||
expect(evaluation).toEqual({ healthy: false, reason: "stale-socket" });
|
||||
});
|
||||
|
||||
it("skips stale-socket detection for telegram accounts without explicit polling mode", () => {
|
||||
it("skips stale-socket detection when an allowlisted health-check mode is missing", () => {
|
||||
const evaluation = evaluateChannelHealth(
|
||||
{
|
||||
running: true,
|
||||
@@ -168,16 +169,17 @@ describe("evaluateChannelHealth", () => {
|
||||
lastEventAt: 0,
|
||||
},
|
||||
{
|
||||
channelId: "telegram",
|
||||
channelId: "example",
|
||||
now: 100_000,
|
||||
channelConnectGraceMs: 10_000,
|
||||
staleEventThresholdMs: 30_000,
|
||||
staleSocketHealthCheckModes: ["polling"],
|
||||
},
|
||||
);
|
||||
expect(evaluation).toEqual({ healthy: true, reason: "healthy" });
|
||||
});
|
||||
|
||||
it("skips stale-socket detection for telegram accounts with malformed mode", () => {
|
||||
it("skips stale-socket detection when the health-check mode is malformed", () => {
|
||||
const evaluation = evaluateChannelHealth(
|
||||
{
|
||||
running: true,
|
||||
@@ -189,10 +191,11 @@ describe("evaluateChannelHealth", () => {
|
||||
mode: { polling: true } as unknown as string,
|
||||
},
|
||||
{
|
||||
channelId: "telegram",
|
||||
channelId: "example",
|
||||
now: 100_000,
|
||||
channelConnectGraceMs: 10_000,
|
||||
staleEventThresholdMs: 30_000,
|
||||
staleSocketHealthCheckModes: ["polling"],
|
||||
},
|
||||
);
|
||||
expect(evaluation).toEqual({ healthy: true, reason: "healthy" });
|
||||
|
||||
@@ -36,6 +36,7 @@ export type ChannelHealthPolicy = {
|
||||
staleEventThresholdMs: number;
|
||||
channelConnectGraceMs: number;
|
||||
skipStaleSocketCheck?: boolean;
|
||||
staleSocketHealthCheckModes?: readonly string[];
|
||||
};
|
||||
|
||||
export type ChannelRestartReason =
|
||||
@@ -55,6 +56,19 @@ const BUSY_ACTIVITY_STALE_THRESHOLD_MS = 25 * 60_000;
|
||||
export const DEFAULT_CHANNEL_STALE_EVENT_THRESHOLD_MS = 30 * 60_000;
|
||||
export const DEFAULT_CHANNEL_CONNECT_GRACE_MS = 120_000;
|
||||
|
||||
function shouldCheckStaleSocketForMode(
|
||||
mode: string | undefined,
|
||||
healthCheckModes: readonly string[] | undefined,
|
||||
): boolean {
|
||||
if (healthCheckModes) {
|
||||
const normalizedModes = new Set(
|
||||
healthCheckModes.map((entry) => entry.trim().toLowerCase()).filter(Boolean),
|
||||
);
|
||||
return Boolean(mode && normalizedModes.has(mode));
|
||||
}
|
||||
return mode !== "webhook";
|
||||
}
|
||||
|
||||
export function evaluateChannelHealth(
|
||||
snapshot: ChannelHealthSnapshot,
|
||||
policy: ChannelHealthPolicy,
|
||||
@@ -112,14 +126,11 @@ export function evaluateChannelHealth(
|
||||
if (snapshot.connected === false) {
|
||||
return { healthy: false, reason: "disconnected" };
|
||||
}
|
||||
// Telegram only has reliable stale-socket liveness in explicit polling mode.
|
||||
// Webhook accounts and malformed legacy mode values do not have a persistent
|
||||
// outgoing socket to age-check.
|
||||
const shouldCheckStaleSocket =
|
||||
policy.skipStaleSocketCheck !== true &&
|
||||
snapshot.connected === true &&
|
||||
lastEventAt != null &&
|
||||
(policy.channelId === "telegram" ? mode === "polling" : mode !== "webhook");
|
||||
shouldCheckStaleSocketForMode(mode, policy.staleSocketHealthCheckModes);
|
||||
if (shouldCheckStaleSocket) {
|
||||
if (lastStartAt != null && lastEventAt < lastStartAt) {
|
||||
const lifecycleEventGap = Math.max(0, policy.now - lastStartAt);
|
||||
|
||||
@@ -64,12 +64,14 @@ export function createReadinessChecker(deps: {
|
||||
if (!accountSnapshot) {
|
||||
continue;
|
||||
}
|
||||
const channelPluginStatus = getChannelPlugin(channelId)?.status;
|
||||
const policy: ChannelHealthPolicy = {
|
||||
now,
|
||||
staleEventThresholdMs: DEFAULT_CHANNEL_STALE_EVENT_THRESHOLD_MS,
|
||||
channelConnectGraceMs: DEFAULT_CHANNEL_CONNECT_GRACE_MS,
|
||||
channelId,
|
||||
skipStaleSocketCheck: getChannelPlugin(channelId)?.status?.skipStaleSocketHealthCheck,
|
||||
skipStaleSocketCheck: channelPluginStatus?.skipStaleSocketHealthCheck,
|
||||
staleSocketHealthCheckModes: channelPluginStatus?.staleSocketHealthCheckModes,
|
||||
};
|
||||
const health = evaluateChannelHealth(accountSnapshot, policy);
|
||||
if (!health.healthy && !shouldIgnoreReadinessFailure(accountSnapshot, health)) {
|
||||
|
||||
@@ -50,6 +50,10 @@ const CORE_SECRET_SURFACE_GUARDS = [
|
||||
path: "src/plugin-sdk/command-auth.ts",
|
||||
forbiddenPatterns: [/\bpluginId:\s*"telegram"/],
|
||||
},
|
||||
{
|
||||
path: "src/gateway/channel-health-policy.ts",
|
||||
forbiddenPatterns: [/\btelegram\b/],
|
||||
},
|
||||
] as const;
|
||||
|
||||
describe("channel secret contract surface guardrails", () => {
|
||||
|
||||
Reference in New Issue
Block a user