gateway: close health monitor review threads

This commit is contained in:
Tak Hoffman 2026-03-14 20:40:52 -05:00
parent 226c2e3aa0
commit f1bc3d157c
4 changed files with 82 additions and 0 deletions

View File

@ -212,6 +212,37 @@ describe("gateway.channelHealthCheckMinutes", () => {
expect(res.issues[0]?.path).toBe("gateway.channelHealthCheckMinutes");
}
});
it("rejects stale thresholds shorter than the health check interval", () => {
const res = validateConfigObject({
gateway: {
channelHealthCheckMinutes: 5,
channelStaleEventThresholdMinutes: 4,
},
});
expect(res.ok).toBe(false);
if (!res.ok) {
expect(res.issues[0]?.path).toBe("gateway.channelStaleEventThresholdMinutes");
}
});
it("accepts stale thresholds that match or exceed the health check interval", () => {
const equal = validateConfigObject({
gateway: {
channelHealthCheckMinutes: 5,
channelStaleEventThresholdMinutes: 5,
},
});
expect(equal.ok).toBe(true);
const greater = validateConfigObject({
gateway: {
channelHealthCheckMinutes: 5,
channelStaleEventThresholdMinutes: 6,
},
});
expect(greater.ok).toBe(true);
});
});
describe("cron webhook schema", () => {

View File

@ -835,6 +835,21 @@ export const OpenClawSchema = z
.optional(),
})
.strict()
.superRefine((gateway, ctx) => {
if (
gateway.channelStaleEventThresholdMinutes != null &&
gateway.channelHealthCheckMinutes != null &&
gateway.channelHealthCheckMinutes !== 0 &&
gateway.channelStaleEventThresholdMinutes < gateway.channelHealthCheckMinutes
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["channelStaleEventThresholdMinutes"],
message:
"channelStaleEventThresholdMinutes should be >= channelHealthCheckMinutes to avoid delayed stale detection",
});
}
})
.optional(),
memory: MemorySchema,
skills: z

View File

@ -235,4 +235,27 @@ describe("server-channels auto restart", () => {
expect(manager.isHealthMonitorEnabled("discord", "router-d")).toBe(false);
});
it("falls back to channel-level health monitor overrides when account resolution omits them", () => {
installTestRegistry(
createTestPlugin({
resolveAccount: () => ({
enabled: true,
configured: true,
}),
}),
);
const manager = createManager({
loadConfig: () => ({
channels: {
discord: {
healthMonitor: { enabled: false },
},
},
}),
});
expect(manager.isHealthMonitorEnabled("discord", DEFAULT_ACCOUNT_ID)).toBe(false);
});
});

View File

@ -131,11 +131,24 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
}
| undefined;
const accountOverride = resolvedAccount?.healthMonitor?.enabled;
const channelOverride = (
cfg.channels?.[channelId] as
| {
healthMonitor?: {
enabled?: boolean;
};
}
| undefined
)?.healthMonitor?.enabled;
if (typeof accountOverride === "boolean") {
return accountOverride;
}
if (typeof channelOverride === "boolean") {
return channelOverride;
}
return true;
};