mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(cron): preserve canonical failure alert routes (#116933)
This commit is contained in:
committed by
GitHub
parent
00194139ba
commit
2433fa213c
@@ -9,25 +9,64 @@ import { resolveFailureAlert } from "./failure-alerts.js";
|
|||||||
import { createCronServiceState, type DeferredCronNotifications } from "./state.js";
|
import { createCronServiceState, type DeferredCronNotifications } from "./state.js";
|
||||||
import { applyJobResult } from "./timer.js";
|
import { applyJobResult } from "./timer.js";
|
||||||
|
|
||||||
|
function stripTestTargetPrefix(raw: string, prefixes: readonly string[]): string | undefined {
|
||||||
|
const target = raw
|
||||||
|
.trim()
|
||||||
|
.replace(new RegExp(`^(?:${prefixes.join("|")}):`, "i"), "")
|
||||||
|
.trim();
|
||||||
|
return target || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDiscordTestTarget(raw: string): string | undefined {
|
||||||
|
const target = raw.trim().toLowerCase();
|
||||||
|
if (!target) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (target.startsWith("discord:channel:")) {
|
||||||
|
return target.slice("discord:".length);
|
||||||
|
}
|
||||||
|
if (target.startsWith("discord:")) {
|
||||||
|
return `user:${target.slice("discord:".length)}`;
|
||||||
|
}
|
||||||
|
return /^(channel|user):/.test(target) ? target : `channel:${target}`;
|
||||||
|
}
|
||||||
|
|
||||||
describe("cron failure alert account routing", () => {
|
describe("cron failure alert account routing", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
const pluginSpecs = [
|
||||||
|
{
|
||||||
|
id: "telegram",
|
||||||
|
aliases: [],
|
||||||
|
targetPrefixes: ["telegram", "tg"],
|
||||||
|
normalizeTarget: (raw: string) => {
|
||||||
|
const target = stripTestTargetPrefix(raw, ["telegram", "tg"]);
|
||||||
|
return target ? `telegram:${target}` : undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "googlechat",
|
||||||
|
aliases: ["gchat", "google-chat"],
|
||||||
|
targetPrefixes: ["googlechat", "google-chat", "gchat"],
|
||||||
|
normalizeTarget: (raw: string) =>
|
||||||
|
stripTestTargetPrefix(raw, ["googlechat", "google-chat", "gchat"]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "discord",
|
||||||
|
aliases: [],
|
||||||
|
targetPrefixes: ["discord"],
|
||||||
|
normalizeTarget: normalizeDiscordTestTarget,
|
||||||
|
},
|
||||||
|
];
|
||||||
setActivePluginRegistry(
|
setActivePluginRegistry(
|
||||||
createTestRegistry(
|
createTestRegistry(
|
||||||
[
|
pluginSpecs.map(({ id, aliases, targetPrefixes, normalizeTarget }) => {
|
||||||
{ id: "telegram", aliases: [], targetPrefixes: ["telegram", "tg"] },
|
|
||||||
{
|
|
||||||
id: "googlechat",
|
|
||||||
aliases: ["gchat", "google-chat"],
|
|
||||||
targetPrefixes: ["googlechat", "google-chat", "gchat"],
|
|
||||||
},
|
|
||||||
].map(({ id, aliases, targetPrefixes }) => {
|
|
||||||
const plugin = createChannelTestPluginBase({ id });
|
const plugin = createChannelTestPluginBase({ id });
|
||||||
return {
|
return {
|
||||||
pluginId: id,
|
pluginId: id,
|
||||||
plugin: {
|
plugin: {
|
||||||
...plugin,
|
...plugin,
|
||||||
meta: { ...plugin.meta, aliases },
|
meta: { ...plugin.meta, aliases },
|
||||||
messaging: { targetPrefixes },
|
messaging: { targetPrefixes, normalizeTarget },
|
||||||
},
|
},
|
||||||
source: `test:${id}`,
|
source: `test:${id}`,
|
||||||
};
|
};
|
||||||
@@ -199,6 +238,19 @@ describe("cron failure alert account routing", () => {
|
|||||||
jobAlert: undefined,
|
jobAlert: undefined,
|
||||||
expected: { channel: "slack", to: undefined, accountId: undefined },
|
expected: { channel: "slack", to: undefined, accountId: undefined },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "does not equate Discord user and channel targets with the same id",
|
||||||
|
globalAlert: { enabled: true, after: 1 },
|
||||||
|
deliveryChannel: "discord",
|
||||||
|
deliveryTo: "1234567890",
|
||||||
|
jobAlert: { channel: "discord", to: "discord:1234567890" },
|
||||||
|
expected: {
|
||||||
|
channel: "discord",
|
||||||
|
to: "discord:1234567890",
|
||||||
|
accountId: undefined,
|
||||||
|
threadId: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "does not inherit the primary account for a webhook",
|
name: "does not inherit the primary account for a webhook",
|
||||||
globalAlert: {
|
globalAlert: {
|
||||||
@@ -309,6 +361,13 @@ describe("cron failure alert account routing", () => {
|
|||||||
failureAlert: { channel: "googlechat", to: "googlechat:RoomA" },
|
failureAlert: { channel: "googlechat", to: "googlechat:RoomA" },
|
||||||
expectedChannel: "googlechat",
|
expectedChannel: "googlechat",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "plugin-normalized equivalent",
|
||||||
|
deliveryChannel: "discord",
|
||||||
|
deliveryTo: "1234567890",
|
||||||
|
failureAlert: { channel: "discord", to: "discord:channel:1234567890" },
|
||||||
|
expectedChannel: "discord",
|
||||||
|
},
|
||||||
])("keeps the primary account and topic on $name failure alerts", (testCase) => {
|
])("keeps the primary account and topic on $name failure alerts", (testCase) => {
|
||||||
const { failureAlert } = testCase;
|
const { failureAlert } = testCase;
|
||||||
const sendCronFailureAlert = vi.fn(async () => undefined);
|
const sendCronFailureAlert = vi.fn(async () => undefined);
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/s
|
|||||||
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||||
import type { FailoverReason } from "../../agents/embedded-agent-helpers/types.js";
|
import type { FailoverReason } from "../../agents/embedded-agent-helpers/types.js";
|
||||||
import { normalizeAnyChannelId } from "../../channels/registry-normalize.js";
|
import { normalizeAnyChannelId } from "../../channels/registry-normalize.js";
|
||||||
import {
|
import { resolveTargetPrefixedChannel } from "../../infra/outbound/channel-target-prefix.js";
|
||||||
resolveTargetPrefixedChannel,
|
import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js";
|
||||||
stripTargetProviderPrefix,
|
|
||||||
} from "../../infra/outbound/channel-target-prefix.js";
|
|
||||||
import type { CronFailureNotificationDelivery, CronJob, CronMessageChannel } from "../types.js";
|
import type { CronFailureNotificationDelivery, CronJob, CronMessageChannel } from "../types.js";
|
||||||
import type { CronServiceState, DeferredCronNotifications } from "./state.js";
|
import type { CronServiceState, DeferredCronNotifications } from "./state.js";
|
||||||
|
|
||||||
@@ -53,11 +51,12 @@ function resolveFailureAlertChannel(channel: unknown, to?: string): CronMessageC
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeFailureAlertRecipient(channel: CronMessageChannel, to: string): string {
|
function normalizeFailureAlertRecipient(channel: CronMessageChannel, to: string): string {
|
||||||
if (resolveTargetPrefixedChannel(to) !== channel) {
|
try {
|
||||||
|
return normalizeTargetForProvider(channel, to) ?? to;
|
||||||
|
} catch {
|
||||||
|
// Invalid loaded targets are distinct routes; they must not block run finalization.
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
// Canonicalize loaded-provider aliases only; recipient/topic ids can be case-sensitive.
|
|
||||||
return stripTargetProviderPrefix(to, to.slice(0, to.indexOf(":")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeTo(input: unknown): string | undefined {
|
function normalizeTo(input: unknown): string | undefined {
|
||||||
|
|||||||
Reference in New Issue
Block a user