import { SystemRole } from "@capakraken/shared"; import type { TRPCContext } from "../../trpc.js"; import { withToolAccess, type ToolContext, type ToolDef, type ToolExecutor } from "./shared.js"; type AssistantToolErrorResult = { error: string }; type NotificationCategory = "NOTIFICATION" | "REMINDER" | "TASK" | "APPROVAL"; type NotificationPriority = "LOW" | "NORMAL" | "HIGH" | "URGENT"; type TaskStatus = "OPEN" | "IN_PROGRESS" | "DONE" | "DISMISSED"; type DeliveryChannel = "in_app" | "email" | "both"; type ReminderRecurrence = "daily" | "weekly" | "monthly"; type BroadcastTargetType = "user" | "role" | "project" | "orgUnit" | "all"; type NotificationRecord = { id: string; }; type TaskRecord = { id: string; taskStatus?: TaskStatus | null; }; type ReminderRecord = { id: string; }; type BroadcastRecord = { id: string; recipientCount?: number | null; }; type NotificationsTasksDeps = { createNotificationCaller: (ctx: TRPCContext) => { list: (params: { unreadOnly?: boolean; limit?: number }) => Promise; markRead: (params: { id?: string }) => Promise; unreadCount: () => Promise; create: (params: { userId: string; type: string; title: string; body?: string; entityId?: string; entityType?: string; category?: NotificationCategory; priority?: NotificationPriority; link?: string; taskStatus?: TaskStatus; taskAction?: string; assigneeId?: string; dueDate?: Date; channel?: DeliveryChannel; senderId?: string; }) => Promise; listTasks: (params: { status?: TaskStatus; includeAssigned?: boolean; limit?: number; }) => Promise; taskCounts: () => Promise; getTaskDetail: (params: { id: string }) => Promise; updateTaskStatus: (params: { id: string; status: TaskStatus }) => Promise; executeTaskAction: (params: { id: string }) => Promise<{ task: unknown; actionResult: { message: string }; }>; createReminder: (params: { title: string; remindAt: Date; body?: string; recurrence?: ReminderRecurrence; entityId?: string; entityType?: string; link?: string; }) => Promise; listReminders: (params: { limit?: number }) => Promise; updateReminder: (params: { id: string; title?: string; body?: string; remindAt?: Date; recurrence?: ReminderRecurrence | null; }) => Promise; deleteReminder: (params: { id: string }) => Promise; createTask: (params: { userId: string; title: string; body?: string; priority?: NotificationPriority; dueDate?: Date; taskAction?: string; entityId?: string; entityType?: string; link?: string; channel?: DeliveryChannel; }) => Promise; assignTask: (params: { id: string; assigneeId: string }) => Promise; createBroadcast: (params: { title: string; targetType: BroadcastTargetType; body?: string; link?: string; category?: NotificationCategory; priority?: NotificationPriority; channel?: DeliveryChannel; targetValue?: string; scheduledAt?: Date; taskAction?: string; dueDate?: Date; }) => Promise; listBroadcasts: (params: { limit?: number }) => Promise; getBroadcastById: (params: { id: string }) => Promise; delete: (params: { id: string }) => Promise; }; createScopedCallerContext: (ctx: ToolContext) => TRPCContext; parseDateTime: (value: string, fieldName: string) => Date; parseOptionalDateTime: (value: string | undefined, fieldName: string) => Date | undefined; toAssistantTaskNotFoundError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantTaskActionError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantTaskAssignmentError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantBroadcastNotFoundError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantReminderNotFoundError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantNotificationReadError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantNotificationDeletionError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantReminderCreationError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantNotificationCreationError: ( error: unknown, context: "notification" | "task" | "broadcast", ) => AssistantToolErrorResult | null; }; export const notificationInboxToolDefinitions: ToolDef[] = withToolAccess([ { type: "function", function: { name: "list_notifications", description: "List recent notifications for the current user.", parameters: { type: "object", properties: { unreadOnly: { type: "boolean", description: "Only show unread. Default: false" }, limit: { type: "integer", description: "Max results. Default: 20" }, }, }, }, }, { type: "function", function: { name: "mark_notification_read", description: "Mark one notification as read, or all unread notifications when no notificationId is provided. Always confirm first.", parameters: { type: "object", properties: { notificationId: { type: "string", description: "Notification ID. Omit to mark all unread notifications as read." }, }, }, }, }, { type: "function", function: { name: "get_unread_notification_count", description: "Count unread notifications for the current user.", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "create_notification", description: "Create a notification or task-style notification for a specific user. Manager or admin role required. Always confirm first.", parameters: { type: "object", properties: { userId: { type: "string", description: "Target user ID." }, type: { type: "string", description: "Notification type code." }, title: { type: "string", description: "Title." }, body: { type: "string", description: "Optional body text." }, entityId: { type: "string", description: "Optional linked entity ID." }, entityType: { type: "string", description: "Optional linked entity type." }, category: { type: "string", enum: ["NOTIFICATION", "REMINDER", "TASK", "APPROVAL"] }, priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"] }, link: { type: "string", description: "Optional deep link." }, taskStatus: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"] }, taskAction: { type: "string", description: "Optional machine-readable task action." }, assigneeId: { type: "string", description: "Optional assignee user ID." }, dueDate: { type: "string", format: "date-time", description: "Optional due date." }, channel: { type: "string", enum: ["in_app", "email", "both"] }, senderId: { type: "string", description: "Optional sender override." }, }, required: ["userId", "type", "title"], }, }, }, ], { create_notification: { allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER], }, }); export const notificationTaskToolDefinitions: ToolDef[] = withToolAccess([ { type: "function", function: { name: "list_tasks", description: "List tasks and approvals for the current user via the real notification router, optionally including tasks assigned to them.", parameters: { type: "object", properties: { status: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"], description: "Optional task status filter" }, includeAssigned: { type: "boolean", description: "Include tasks where the current user is assignee as well as owner. Default: true." }, limit: { type: "integer", description: "Max results. Default: 20." }, }, }, }, }, { type: "function", function: { name: "get_task_counts", description: "Get open, in-progress, done, dismissed, and overdue task counts for the current user.", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "get_task_detail", description: "Get details of a specific task/notification including linked entity information.", parameters: { type: "object", properties: { taskId: { type: "string", description: "Notification/task ID" }, }, required: ["taskId"], }, }, }, { type: "function", function: { name: "update_task_status", description: "Update the status of a task. Mark as IN_PROGRESS, DONE, or DISMISSED.", parameters: { type: "object", properties: { taskId: { type: "string", description: "Task/notification ID" }, status: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"], description: "New status" }, }, required: ["taskId", "status"], }, }, }, { type: "function", function: { name: "execute_task_action", description: "Execute the machine-readable action associated with a task. For example: approve a vacation, confirm an assignment, etc. The action is encoded in the task's taskAction field.", parameters: { type: "object", properties: { taskId: { type: "string", description: "Task/notification ID containing the action to execute" }, }, required: ["taskId"], }, }, }, { type: "function", function: { name: "create_reminder", description: "Create a personal reminder for the current user via the real notification router. Always confirm first.", parameters: { type: "object", properties: { title: { type: "string", description: "Reminder title" }, body: { type: "string", description: "Optional details" }, remindAt: { type: "string", format: "date-time", description: "When to remind (ISO 8601 datetime)" }, recurrence: { type: "string", enum: ["daily", "weekly", "monthly"], description: "Optional recurrence pattern" }, entityId: { type: "string", description: "Optional: linked entity ID (project, resource, etc.)" }, entityType: { type: "string", description: "Optional: entity type (project, resource, vacation, etc.)" }, link: { type: "string", description: "Optional deep link." }, }, required: ["title", "remindAt"], }, }, }, { type: "function", function: { name: "list_reminders", description: "List personal reminders for the current user via the real notification router.", parameters: { type: "object", properties: { limit: { type: "integer", description: "Max results. Default: 20." }, }, }, }, }, { type: "function", function: { name: "update_reminder", description: "Update a personal reminder via the real notification router. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "Reminder notification ID." }, title: { type: "string", description: "Optional reminder title." }, body: { type: "string", description: "Optional reminder body." }, remindAt: { type: "string", format: "date-time", description: "Optional reminder timestamp." }, recurrence: { type: ["string", "null"], enum: ["daily", "weekly", "monthly", null], description: "Optional recurrence update. Use null to clear recurrence." }, }, required: ["id"], }, }, }, { type: "function", function: { name: "delete_reminder", description: "Delete a personal reminder via the real notification router. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "Reminder notification ID." }, }, required: ["id"], }, }, }, { type: "function", function: { name: "create_task_for_user", description: "Create a task for a specific user via the real notification router. Manager or admin role required. Always confirm first.", parameters: { type: "object", properties: { userId: { type: "string", description: "Target user ID" }, title: { type: "string", description: "Task title" }, body: { type: "string", description: "Task description" }, priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"], description: "Priority (default NORMAL)" }, dueDate: { type: "string", format: "date-time", description: "Optional due date (ISO 8601)" }, taskAction: { type: "string", description: "Optional machine-readable action (format: action_name:entity_id)" }, entityId: { type: "string", description: "Optional linked entity ID" }, entityType: { type: "string", description: "Optional entity type" }, link: { type: "string", description: "Optional deep link." }, channel: { type: "string", enum: ["in_app", "email", "both"], description: "Delivery channel. Default: in_app." }, }, required: ["userId", "title"], }, }, }, { type: "function", function: { name: "assign_task", description: "Assign or reassign a task to another user via the real notification router. Manager or admin role required. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "Task notification ID." }, assigneeId: { type: "string", description: "User ID to assign." }, }, required: ["id", "assigneeId"], }, }, }, { type: "function", function: { name: "send_broadcast", description: "Create and send a broadcast notification via the real notification router. Manager or admin role required. Always confirm first.", parameters: { type: "object", properties: { title: { type: "string", description: "Notification title" }, body: { type: "string", description: "Notification body" }, targetType: { type: "string", enum: ["user", "role", "project", "orgUnit", "all"], description: "Target audience type" }, targetValue: { type: "string", description: "Target value: user ID, role name (ADMIN/MANAGER/CONTROLLER/USER/VIEWER), project ID, or org unit ID" }, category: { type: "string", enum: ["NOTIFICATION", "REMINDER", "TASK", "APPROVAL"], description: "Broadcast category. Default: NOTIFICATION." }, priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"], description: "Priority (default NORMAL)" }, channel: { type: "string", enum: ["in_app", "email", "both"], description: "Delivery channel (default in_app)" }, link: { type: "string", description: "Optional deep-link URL" }, scheduledAt: { type: "string", format: "date-time", description: "Optional scheduled send timestamp." }, taskAction: { type: "string", description: "Optional machine-readable task action for task-like broadcasts." }, dueDate: { type: "string", format: "date-time", description: "Optional due date for task-like broadcasts." }, }, required: ["title", "targetType"], }, }, }, { type: "function", function: { name: "list_broadcasts", description: "List notification broadcasts via the real notification router. Manager or admin role required.", parameters: { type: "object", properties: { limit: { type: "integer", description: "Max results. Default: 20." }, }, }, }, }, { type: "function", function: { name: "get_broadcast_detail", description: "Get one notification broadcast via the real notification router. Manager or admin role required.", parameters: { type: "object", properties: { id: { type: "string", description: "Broadcast ID." }, }, required: ["id"], }, }, }, { type: "function", function: { name: "delete_notification", description: "Delete one of the current user's own notifications via the real notification router. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "Notification ID." }, }, required: ["id"], }, }, }, ], { create_task_for_user: { allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER], }, assign_task: { allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER], }, send_broadcast: { allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER], }, list_broadcasts: { allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER], }, get_broadcast_detail: { allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER], }, }); export function createNotificationsTasksExecutors( deps: NotificationsTasksDeps, ): Record { return { async list_notifications(params: { unreadOnly?: boolean; limit?: number }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); return caller.list({ ...(params.unreadOnly !== undefined ? { unreadOnly: params.unreadOnly } : {}), ...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}), }); }, async mark_notification_read(params: { notificationId?: string }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); try { await caller.markRead({ ...(params.notificationId !== undefined ? { id: params.notificationId } : {}), }); } catch (error) { const mapped = deps.toAssistantNotificationReadError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, message: params.notificationId ? "Notification marked as read." : "All unread notifications marked as read.", }; }, async get_unread_notification_count(_params: Record, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); const count = await caller.unreadCount(); return { count }; }, async create_notification(params: { userId: string; type: string; title: string; body?: string; entityId?: string; entityType?: string; category?: NotificationCategory; priority?: NotificationPriority; link?: string; taskStatus?: TaskStatus; taskAction?: string; assigneeId?: string; dueDate?: string; channel?: DeliveryChannel; senderId?: string; }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); const dueDate = deps.parseOptionalDateTime(params.dueDate, "dueDate"); let notification; try { notification = await caller.create({ userId: params.userId, type: params.type, title: params.title, ...(params.body !== undefined ? { body: params.body } : {}), ...(params.entityId !== undefined ? { entityId: params.entityId } : {}), ...(params.entityType !== undefined ? { entityType: params.entityType } : {}), ...(params.category !== undefined ? { category: params.category } : {}), ...(params.priority !== undefined ? { priority: params.priority } : {}), ...(params.link !== undefined ? { link: params.link } : {}), ...(params.taskStatus !== undefined ? { taskStatus: params.taskStatus } : {}), ...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}), ...(params.assigneeId !== undefined ? { assigneeId: params.assigneeId } : {}), ...(dueDate ? { dueDate } : {}), ...(params.channel !== undefined ? { channel: params.channel } : {}), ...(params.senderId !== undefined ? { senderId: params.senderId } : {}), }); } catch (error) { const mapped = deps.toAssistantNotificationCreationError(error, "notification"); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, notification, notificationId: notification?.id ?? null, message: `Created notification "${params.title}".`, }; }, async list_tasks(params: { status?: TaskStatus; includeAssigned?: boolean; limit?: number; }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); return caller.listTasks({ ...(params.status !== undefined ? { status: params.status } : {}), ...(params.includeAssigned !== undefined ? { includeAssigned: params.includeAssigned } : {}), ...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}), }); }, async get_task_counts(_params: Record, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); return caller.taskCounts(); }, async get_task_detail(params: { taskId: string }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); try { return await caller.getTaskDetail({ id: params.taskId }); } catch (error) { const mapped = deps.toAssistantTaskNotFoundError(error); if (mapped) { return mapped; } throw error; } }, async update_task_status(params: { taskId: string; status: string }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); let task; try { task = await caller.updateTaskStatus({ id: params.taskId, status: params.status as TaskStatus, }); } catch (error) { const mapped = deps.toAssistantTaskNotFoundError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, task, message: `Task status updated to ${task.taskStatus ?? params.status}.`, }; }, async execute_task_action(params: { taskId: string }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); let result; try { result = await caller.executeTaskAction({ id: params.taskId }); } catch (error) { const mapped = deps.toAssistantTaskActionError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, task: result.task, message: result.actionResult.message, }; }, async create_reminder(params: { title: string; body?: string; remindAt: string; recurrence?: string; entityId?: string; entityType?: string; link?: string; }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); if (!params.title.trim()) { return { error: "Reminder title is required." }; } if (params.title.length > 200) { return { error: "Reminder title must be at most 200 characters." }; } if (params.body !== undefined && params.body.length > 2000) { return { error: "Reminder body must be at most 2000 characters." }; } if ( params.recurrence !== undefined && !["daily", "weekly", "monthly"].includes(params.recurrence) ) { return { error: `Invalid recurrence: ${params.recurrence}. Valid values: daily, weekly, monthly.`, }; } const remindAt = deps.parseDateTime(params.remindAt, "remindAt"); let reminder; try { reminder = await caller.createReminder({ title: params.title, remindAt, ...(params.body !== undefined ? { body: params.body } : {}), ...(params.recurrence !== undefined ? { recurrence: params.recurrence as ReminderRecurrence } : {}), ...(params.entityId !== undefined ? { entityId: params.entityId } : {}), ...(params.entityType !== undefined ? { entityType: params.entityType } : {}), ...(params.link !== undefined ? { link: params.link } : {}), }); } catch (error) { const mapped = deps.toAssistantReminderCreationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, reminder, reminderId: reminder.id, message: `Reminder "${params.title}" created.`, }; }, async list_reminders(params: { limit?: number }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); return caller.listReminders({ ...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}), }); }, async update_reminder(params: { id: string; title?: string; body?: string; remindAt?: string; recurrence?: ReminderRecurrence | null; }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); const remindAt = deps.parseOptionalDateTime(params.remindAt, "remindAt"); let reminder; try { reminder = await caller.updateReminder({ id: params.id, ...(params.title !== undefined ? { title: params.title } : {}), ...(params.body !== undefined ? { body: params.body } : {}), ...(remindAt ? { remindAt } : {}), ...(params.recurrence !== undefined ? { recurrence: params.recurrence } : {}), }); } catch (error) { const mapped = deps.toAssistantReminderNotFoundError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, reminder, reminderId: reminder.id, message: `Updated reminder ${params.id}.`, }; }, async delete_reminder(params: { id: string }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); try { await caller.deleteReminder({ id: params.id }); } catch (error) { const mapped = deps.toAssistantReminderNotFoundError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, id: params.id, message: `Deleted reminder ${params.id}.`, }; }, async create_task_for_user(params: { userId: string; title: string; body?: string; priority?: string; dueDate?: string; taskAction?: string; entityId?: string; entityType?: string; link?: string; channel?: DeliveryChannel; }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); const dueDate = deps.parseOptionalDateTime(params.dueDate, "dueDate"); let task; try { task = await caller.createTask({ userId: params.userId, title: params.title, ...(params.body !== undefined ? { body: params.body } : {}), ...(params.priority !== undefined ? { priority: params.priority as NotificationPriority } : {}), ...(dueDate ? { dueDate } : {}), ...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}), ...(params.entityId !== undefined ? { entityId: params.entityId } : {}), ...(params.entityType !== undefined ? { entityType: params.entityType } : {}), ...(params.link !== undefined ? { link: params.link } : {}), ...(params.channel !== undefined ? { channel: params.channel } : {}), }); } catch (error) { const mapped = deps.toAssistantNotificationCreationError(error, "task"); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, task, taskId: task?.id ?? null, message: `Created task "${params.title}" for ${params.userId}.`, }; }, async assign_task(params: { id: string; assigneeId: string }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); let task; try { task = await caller.assignTask(params); } catch (error) { const mapped = deps.toAssistantTaskAssignmentError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, task, taskId: task.id, message: `Assigned task ${params.id} to ${params.assigneeId}.`, }; }, async send_broadcast(params: { title: string; body?: string; targetType: string; targetValue?: string; category?: NotificationCategory; priority?: string; channel?: string; link?: string; scheduledAt?: string; taskAction?: string; dueDate?: string; }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); const scheduledAt = deps.parseOptionalDateTime(params.scheduledAt, "scheduledAt"); const dueDate = deps.parseOptionalDateTime(params.dueDate, "dueDate"); let broadcast; try { broadcast = await caller.createBroadcast({ title: params.title, targetType: params.targetType as BroadcastTargetType, ...(params.body !== undefined ? { body: params.body } : {}), ...(params.link !== undefined ? { link: params.link } : {}), ...(params.category !== undefined ? { category: params.category } : {}), ...(params.priority !== undefined ? { priority: params.priority as NotificationPriority } : {}), ...(params.channel !== undefined ? { channel: params.channel as DeliveryChannel } : {}), ...(params.targetValue !== undefined ? { targetValue: params.targetValue } : {}), ...(scheduledAt ? { scheduledAt } : {}), ...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}), ...(dueDate ? { dueDate } : {}), }); } catch (error) { const mapped = deps.toAssistantNotificationCreationError(error, "broadcast"); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, broadcast, broadcastId: broadcast.id, recipientCount: broadcast.recipientCount ?? 0, message: `Broadcast "${params.title}" created.`, }; }, async list_broadcasts(params: { limit?: number }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); return caller.listBroadcasts({ ...(params.limit !== undefined ? { limit: Math.min(params.limit, 50) } : {}), }); }, async get_broadcast_detail(params: { id: string }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); try { return await caller.getBroadcastById({ id: params.id }); } catch (error) { const mapped = deps.toAssistantBroadcastNotFoundError(error); if (mapped) { return mapped; } throw error; } }, async delete_notification(params: { id: string }, ctx: ToolContext) { const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx)); try { await caller.delete({ id: params.id }); } catch (error) { const mapped = deps.toAssistantNotificationDeletionError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["notification"], success: true, id: params.id, message: `Deleted notification ${params.id}.`, }; }, }; }