fix: preserve notifier routing fallbacks (#1958)

This commit is contained in:
whoisasx 2026-05-21 01:33:34 +05:30
parent 71e8151f6e
commit 9ab18c6846
4 changed files with 65 additions and 6 deletions

View File

@ -159,6 +159,41 @@ describe("startup notifier defaults", () => {
expect(parsed.notificationRouting?.urgent).toEqual(["desktop", "dashboard"]);
});
it("preserves defaults.notifiers fallback semantics when writing startup routes", () => {
writeFileSync(
configPath,
[
"port: 3000",
"defaults:",
" notifiers:",
" - slack",
"notifiers:",
" slack:",
" plugin: slack",
" webhookUrl: https://hooks.slack.com/services/T/B/C",
"projects: {}",
"",
].join("\n"),
);
expect(
ensureStartupNotifierDefaults({
configPath,
dashboardUrl: "http://localhost:3000",
desktopMode: "enable",
}),
).toBe(true);
const parsed = readConfig();
expect(parsed.defaults?.notifiers).toEqual(["slack"]);
expect(parsed.notificationRouting).toEqual({
urgent: ["slack", "desktop", "dashboard"],
action: ["slack", "dashboard"],
warning: ["slack", "dashboard"],
info: ["slack", "dashboard"],
});
});
it("preserves configured manual opt-in notifiers while removing only implicit manual defaults", () => {
writeFileSync(
configPath,

View File

@ -44,6 +44,10 @@ function arraysEqual(left: string[], right: string[]): boolean {
return left.length === right.length && left.every((value, index) => value === right[index]);
}
function hasOwn(record: Record<string, unknown>, key: string): boolean {
return Object.prototype.hasOwnProperty.call(record, key);
}
function hasNotifierConfig(notifiers: Record<string, unknown>, notifierName: string): boolean {
return isRecord(notifiers[notifierName]);
}
@ -227,7 +231,12 @@ export function ensureStartupNotifierDefaults(options: StartupNotifierDefaultsOp
const shouldRemoveDefaultDesktopRouting = desktopMode === "disable-default" && !desktopConfigured;
for (const priority of NOTIFICATION_PRIORITIES) {
const existingRoute = sanitizeNotifierReferences(notifiers, routing[priority]);
const hasExplicitRoute = hasOwn(routing, priority);
const existingRoute = sanitizeNotifierReferences(
notifiers,
hasExplicitRoute ? routing[priority] : defaults["notifiers"],
);
const previousRoute = hasExplicitRoute ? asStringArray(routing[priority]) : existingRoute;
let nextRoute = existingRoute;
if (shouldRemoveDefaultDesktopRouting) {
@ -245,7 +254,7 @@ export function ensureStartupNotifierDefaults(options: StartupNotifierDefaultsOp
}
nextRoute = unique(nextRoute);
if (!arraysEqual(asStringArray(routing[priority]), nextRoute)) {
if (!arraysEqual(previousRoute, nextRoute)) {
routing[priority] = nextRoute;
changed = true;
}

View File

@ -483,6 +483,18 @@ describe("notifier-desktop", () => {
expect(mockExecFile.mock.calls[0][0]).toBe("notify-send");
});
it("keeps action labels on non-macOS even when backend is ao-app", async () => {
mockPlatform.mockReturnValue("linux");
setProcessPlatform("linux");
const notifier = create({ backend: "ao-app", dashboardUrl: "http://localhost:3000" });
const actions: NotifyAction[] = [{ label: "Open PR", url: "https://example.com/pr/1" }];
await notifier.notifyWithActions!(makeEvent(), actions);
expect(mockExecFile.mock.calls[0][0]).toBe("notify-send");
const args = mockExecFile.mock.calls[0][1] as string[];
expect(args.join("\n")).toContain("Open PR");
});
it("uses terminal-notifier for notifyWithActions too", async () => {
const notifier = create({ dashboardUrl: "http://localhost:3000" });
const actions: NotifyAction[] = [{ label: "View", url: "https://example.com" }];

View File

@ -388,6 +388,10 @@ function detectTerminalNotifier(): boolean {
}
}
function usesAoNotifierNativeActions(backend: DesktopBackend, appPath: string): boolean {
return isMac() && (backend === "ao-app" || (backend === "auto" && detectAoNotifierApp(appPath)));
}
/**
* Send a desktop notification using terminal-notifier / osascript (macOS) or
* notify-send (Linux). Falls back gracefully if neither is available.
@ -568,8 +572,7 @@ export function create(config?: Record<string, unknown>): Notifier {
async notifyWithActions(event: OrchestratorEvent, actions: NotifyAction[]): Promise<void> {
const nativeActions = nativeActionPayloads(actions, dashboardUrl);
const content = formatContent(event, actions, {
hiddenActionIndexes:
backend === "ao-app" || (backend === "auto" && detectAoNotifierApp(appPath))
hiddenActionIndexes: usesAoNotifierNativeActions(backend, appPath)
? nativeActionIndexes(actions, dashboardUrl)
: undefined,
});