feat(FN-3008): notify when model settings fall back to defaults
Merges FN-3008 to add a "fallback-used" notification system: the engine now emits events when AI model fallbacks are triggered, dispatches notifications via ntfy/webhook providers, surfaces a session banner in the dashboard, and exposes a settings toggle to enable or disable these alerts. Fusion-Task-Id: FN-3008
This commit is contained in:
@@ -228,6 +228,15 @@ export class NotificationService {
|
||||
);
|
||||
}
|
||||
|
||||
async dispatch(eventType: NotificationEvent, payload: NotificationPayload): Promise<void> {
|
||||
if (!this.notificationsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dedupTaskId = payload.taskId ?? "global";
|
||||
this.maybeNotify(dedupTaskId, eventType, payload);
|
||||
}
|
||||
|
||||
private createTaskPayload(task: Task, event: NotificationEvent): NotificationPayload {
|
||||
return {
|
||||
taskId: task.id,
|
||||
|
||||
@@ -33,7 +33,8 @@ type SupportedNtfyEvent =
|
||||
| "failed"
|
||||
| "awaiting-approval"
|
||||
| "awaiting-user-review"
|
||||
| "planning-awaiting-input";
|
||||
| "planning-awaiting-input"
|
||||
| "fallback-used";
|
||||
|
||||
const SUPPORTED_EVENTS = new Set<SupportedNtfyEvent>([
|
||||
"in-review",
|
||||
@@ -42,6 +43,7 @@ const SUPPORTED_EVENTS = new Set<SupportedNtfyEvent>([
|
||||
"awaiting-approval",
|
||||
"awaiting-user-review",
|
||||
"planning-awaiting-input",
|
||||
"fallback-used",
|
||||
]);
|
||||
|
||||
export class NtfyNotificationProvider implements NotificationProvider {
|
||||
@@ -92,8 +94,9 @@ export class NtfyNotificationProvider implements NotificationProvider {
|
||||
};
|
||||
}
|
||||
|
||||
const taskId = payload.taskId ?? "unknown-task";
|
||||
const taskLike = {
|
||||
id: payload.taskId,
|
||||
id: taskId,
|
||||
title: payload.taskTitle,
|
||||
description: payload.taskDescription ?? "",
|
||||
} as Pick<Task, "id" | "title" | "description"> as Task;
|
||||
@@ -107,35 +110,40 @@ export class NtfyNotificationProvider implements NotificationProvider {
|
||||
|
||||
const contentByEvent: Record<SupportedNtfyEvent, { title: string; message: string; priority: "default" | "high" }> = {
|
||||
"in-review": {
|
||||
title: `Task ${payload.taskId} completed`,
|
||||
title: `Task ${taskId} completed`,
|
||||
message: `Task "${identifier}" is ready for review`,
|
||||
priority: "default",
|
||||
},
|
||||
merged: {
|
||||
title: `Task ${payload.taskId} merged`,
|
||||
title: `Task ${taskId} merged`,
|
||||
message: `Task "${identifier}" has been merged to main`,
|
||||
priority: "default",
|
||||
},
|
||||
failed: {
|
||||
title: `Task ${payload.taskId} failed`,
|
||||
title: `Task ${taskId} failed`,
|
||||
message: `Task "${identifier}" has failed and needs attention`,
|
||||
priority: "high",
|
||||
},
|
||||
"awaiting-approval": {
|
||||
title: `Plan needs approval for ${payload.taskId}`,
|
||||
title: `Plan needs approval for ${taskId}`,
|
||||
message: `Task "${identifier}" needs your approval before it can proceed`,
|
||||
priority: "high",
|
||||
},
|
||||
"awaiting-user-review": {
|
||||
title: `User review needed for ${payload.taskId}`,
|
||||
title: `User review needed for ${taskId}`,
|
||||
message: `Task "${identifier}" needs human review before it can proceed`,
|
||||
priority: "high",
|
||||
},
|
||||
"planning-awaiting-input": {
|
||||
title: `Planning input needed for ${payload.taskId}`,
|
||||
title: `Planning input needed for ${taskId}`,
|
||||
message: `Task "${identifier}" is awaiting your input during planning`,
|
||||
priority: "high",
|
||||
},
|
||||
"fallback-used": {
|
||||
title: `Fallback model used${payload.taskId ? ` for ${payload.taskId}` : ""}`,
|
||||
message: `Fusion switched from ${String(payload.metadata?.primaryModel ?? "primary model")} to ${String(payload.metadata?.fallbackModel ?? "fallback model")} after a retryable failure (${String(payload.metadata?.triggerPoint ?? "unknown trigger")}).`,
|
||||
priority: "high",
|
||||
},
|
||||
};
|
||||
|
||||
const content = contentByEvent[event as SupportedNtfyEvent];
|
||||
|
||||
@@ -133,6 +133,8 @@ export class WebhookNotificationProvider implements NotificationProvider {
|
||||
return `Task "${identifier}" is awaiting your input during planning`;
|
||||
case "gridlock":
|
||||
return "Pipeline gridlocked";
|
||||
case "fallback-used":
|
||||
return `Fusion recovered by switching from ${String(payload.metadata?.primaryModel ?? "primary model")} to ${String(payload.metadata?.fallbackModel ?? "fallback model")} (${String(payload.metadata?.triggerPoint ?? "unknown trigger")})`;
|
||||
default:
|
||||
return `Event "${event}" for task ${identifier}`;
|
||||
}
|
||||
@@ -145,7 +147,7 @@ export class WebhookNotificationProvider implements NotificationProvider {
|
||||
|
||||
const description = payload.taskDescription ?? "";
|
||||
const snippet = description.length > 200 ? `${description.slice(0, 200)}...` : description;
|
||||
return `${payload.taskId}: ${snippet}`;
|
||||
return `${payload.taskId ?? "unknown-task"}: ${snippet}`;
|
||||
}
|
||||
|
||||
private formatPayload(payload: NotificationPayload, message: string): Record<string, unknown> {
|
||||
|
||||
Reference in New Issue
Block a user