mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(channels): surface partial status failures (#122349)
This commit is contained in:
committed by
GitHub
parent
964c8c84c1
commit
3254ececf1
@@ -111,6 +111,18 @@ describe("channels command", () => {
|
||||
expect(lines.join("\n")).toMatch(/eventLoopDelayMaxMs=62000/);
|
||||
});
|
||||
|
||||
it("surfaces top-level partial status warnings", () => {
|
||||
const lines = formatGatewayChannelsStatusLines({
|
||||
partial: true,
|
||||
warnings: ["whatsapp:default status failed: snapshot failed"],
|
||||
channelLabels: {},
|
||||
channelAccounts: {},
|
||||
});
|
||||
|
||||
expect(lines.join("\n")).toMatch(/Channel status is partial/);
|
||||
expect(lines.join("\n")).toContain("whatsapp:default status failed: snapshot failed");
|
||||
});
|
||||
|
||||
it("surfaces transport liveness timestamps in channels status output", () => {
|
||||
const lines = formatGatewayChannelsStatusLines({
|
||||
channelLabels: {
|
||||
|
||||
@@ -76,6 +76,20 @@ export function formatGatewayChannelsStatusLines(payload: Record<string, unknown
|
||||
if (eventLoopLine) {
|
||||
lines.push(theme.warn(`Gateway event loop degraded ${eventLoopLine}`));
|
||||
}
|
||||
const statusWarnings = Array.isArray(payload.warnings)
|
||||
? payload.warnings
|
||||
.filter(
|
||||
(warning): warning is string => typeof warning === "string" && warning.trim().length > 0,
|
||||
)
|
||||
.slice(0, 50)
|
||||
: [];
|
||||
if (payload.partial === true || statusWarnings.length > 0) {
|
||||
lines.push(theme.warn("Channel status is partial:"));
|
||||
for (const warning of statusWarnings) {
|
||||
lines.push(`- ${warning.slice(0, 500)}`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
const channelLabels =
|
||||
payload.channelLabels && typeof payload.channelLabels === "object"
|
||||
? (payload.channelLabels as Record<string, unknown>)
|
||||
|
||||
@@ -357,6 +357,40 @@ describe("channelsHandlers channels.status", () => {
|
||||
expect(String(accountProbe.error)).toContain("probe failed");
|
||||
});
|
||||
|
||||
it("marks account snapshot failures partial", async () => {
|
||||
mocks.resolveChannelAccountSnapshot.mockRejectedValue(new Error("snapshot failed"));
|
||||
|
||||
const payload = await runChannelsStatus({ probe: false, timeoutMs: 1000 });
|
||||
|
||||
expect(payload.partial).toBe(true);
|
||||
expect(payload.warnings).toEqual(["whatsapp:default status failed: Error: snapshot failed"]);
|
||||
const channels = requireGatewayRecord(payload.channels, "channels payload");
|
||||
expect(channels.whatsapp).toEqual({ configured: false });
|
||||
});
|
||||
|
||||
it("isolates a failed channel status task while a sibling succeeds", async () => {
|
||||
const broken = createChannelPlugin({ id: "broken" });
|
||||
broken.config.listAccountIds = () => {
|
||||
throw new Error("channel failed");
|
||||
};
|
||||
configureAutoEnabledChannels([broken, createChannelPlugin({ id: "healthy" })]);
|
||||
mocks.buildChannelUiCatalog.mockImplementation((plugins: Array<{ id: string }>) => ({
|
||||
order: plugins.map((plugin) => plugin.id),
|
||||
labels: {},
|
||||
detailLabels: {},
|
||||
systemImages: {},
|
||||
entries: {},
|
||||
}));
|
||||
|
||||
const payload = await runChannelsStatus({ probe: false, timeoutMs: 1000 });
|
||||
|
||||
expect(payload.partial).toBe(true);
|
||||
expect(payload.warnings).toEqual(["broken channel status failed: Error: channel failed"]);
|
||||
expect(requireGatewayRecord(payload.channels, "channels payload").healthy).toEqual({
|
||||
configured: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("isolates a timed-out channel probe while another channel succeeds", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
|
||||
@@ -510,6 +510,10 @@ export const channelsHandlers: GatewayRequestHandlers = {
|
||||
await buildAccountSnapshot(channelId, plugin, accountId, defaultAccountId),
|
||||
),
|
||||
limit: probe ? CHANNEL_STATUS_PROBE_CONCURRENCY : accountIds.length || 1,
|
||||
onTaskError: (error, index) => {
|
||||
const accountId = accountIds[index] ?? `account ${index + 1}`;
|
||||
statusWarnings.push(`${channelId}:${accountId} status failed: ${formatForLog(error)}`);
|
||||
},
|
||||
});
|
||||
const accounts: ChannelAccountSnapshot[] = [];
|
||||
for (const result of results) {
|
||||
@@ -572,6 +576,10 @@ export const channelsHandlers: GatewayRequestHandlers = {
|
||||
return { pluginId: plugin.id, summary, accounts, defaultAccountId };
|
||||
}),
|
||||
limit: probe ? CHANNEL_STATUS_PROBE_CONCURRENCY : selectedPlugins.length || 1,
|
||||
onTaskError: (error, index) => {
|
||||
const channelId = statusPlugins[index]?.id ?? `channel ${index + 1}`;
|
||||
statusWarnings.push(`${channelId} channel status failed: ${formatForLog(error)}`);
|
||||
},
|
||||
});
|
||||
for (const result of channelResults) {
|
||||
if (result) {
|
||||
@@ -582,7 +590,7 @@ export const channelsHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
if (statusWarnings.length > 0) {
|
||||
payload.partial = true;
|
||||
payload.warnings = statusWarnings.slice(0, 50);
|
||||
payload.warnings = statusWarnings.toSorted().slice(0, 50);
|
||||
}
|
||||
|
||||
respond(true, payload, undefined);
|
||||
|
||||
Reference in New Issue
Block a user