b41c1d2501
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61) Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com> Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
1056 lines
35 KiB
TypeScript
1056 lines
35 KiB
TypeScript
import { SystemRole } from "@nexus/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<unknown>;
|
|
markRead: (params: { id?: string }) => Promise<void>;
|
|
unreadCount: () => Promise<number>;
|
|
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<NotificationRecord | null>;
|
|
listTasks: (params: {
|
|
status?: TaskStatus;
|
|
includeAssigned?: boolean;
|
|
limit?: number;
|
|
}) => Promise<unknown>;
|
|
taskCounts: () => Promise<unknown>;
|
|
getTaskDetail: (params: { id: string }) => Promise<unknown>;
|
|
updateTaskStatus: (params: { id: string; status: TaskStatus }) => Promise<TaskRecord>;
|
|
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<ReminderRecord>;
|
|
listReminders: (params: { limit?: number }) => Promise<unknown>;
|
|
updateReminder: (params: {
|
|
id: string;
|
|
title?: string;
|
|
body?: string;
|
|
remindAt?: Date;
|
|
recurrence?: ReminderRecurrence | null;
|
|
}) => Promise<ReminderRecord>;
|
|
deleteReminder: (params: { id: string }) => Promise<void>;
|
|
createTask: (params: {
|
|
userId: string;
|
|
title: string;
|
|
body?: string;
|
|
priority?: NotificationPriority;
|
|
dueDate?: Date;
|
|
taskAction?: string;
|
|
entityId?: string;
|
|
entityType?: string;
|
|
link?: string;
|
|
channel?: DeliveryChannel;
|
|
}) => Promise<TaskRecord | null>;
|
|
assignTask: (params: { id: string; assigneeId: string }) => Promise<TaskRecord>;
|
|
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<BroadcastRecord>;
|
|
listBroadcasts: (params: { limit?: number }) => Promise<unknown>;
|
|
getBroadcastById: (params: { id: string }) => Promise<unknown>;
|
|
delete: (params: { id: string }) => Promise<void>;
|
|
};
|
|
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<string, ToolExecutor> {
|
|
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<string, never>, 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<string, never>, 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}.`,
|
|
};
|
|
},
|
|
};
|
|
}
|