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 UserRecord = { id: string; name: string | null; systemRole?: SystemRole | string; }; type UserAdminDeps = { createUserCaller: (ctx: TRPCContext) => { list: () => Promise; create: (params: { email: string; name: string; systemRole?: SystemRole; password: string; }) => Promise; setPassword: (params: { userId: string; password: string }) => Promise>; updateRole: (params: { id: string; systemRole: SystemRole }) => Promise; updateName: (params: { id: string; name: string }) => Promise; linkResource: ( params: { userId: string; resourceId: string | null }, ) => Promise>; autoLinkAllByEmail: () => Promise & { linked: number }>; setPermissions: (params: { userId: string; overrides: { granted?: string[]; denied?: string[]; chapterIds?: string[]; } | null; }) => Promise; resetPermissions: (params: { userId: string }) => Promise; getEffectivePermissions: (params: { userId: string }) => Promise; disableTotp: (params: { userId: string }) => Promise>; }; createScopedCallerContext: (ctx: ToolContext) => TRPCContext; toAssistantUserMutationError: ( error: unknown, mode?: "create" | "password", ) => AssistantToolErrorResult | null; toAssistantUserResourceLinkError: ( error: unknown, ) => AssistantToolErrorResult | null; }; export const userAdminToolDefinitions: ToolDef[] = withToolAccess([ { type: "function", function: { name: "list_users", description: "List all system users via the admin user router, including role and MFA state. Admin role required.", parameters: { type: "object", properties: { limit: { type: "integer", description: "Max results. Default: 50" }, }, }, }, }, { type: "function", function: { name: "create_user", description: "Create a new system user and auto-link a matching resource by email when possible. Admin role required. Always confirm first.", parameters: { type: "object", properties: { email: { type: "string", description: "User email address." }, name: { type: "string", description: "Display name." }, systemRole: { type: "string", enum: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"], description: "Initial system role." }, password: { type: "string", description: "Initial password, minimum 8 characters." }, }, required: ["email", "name", "password"], }, }, }, { type: "function", function: { name: "set_user_password", description: "Reset a user's password. Admin role required. Always confirm first.", parameters: { type: "object", properties: { userId: { type: "string", description: "User ID." }, password: { type: "string", description: "New password, minimum 8 characters." }, }, required: ["userId", "password"], }, }, }, { type: "function", function: { name: "update_user_role", description: "Change a user's system role. Admin role required. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "User ID." }, systemRole: { type: "string", enum: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] }, }, required: ["id", "systemRole"], }, }, }, { type: "function", function: { name: "update_user_name", description: "Rename a user. Admin role required. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "User ID." }, name: { type: "string", description: "New display name." }, }, required: ["id", "name"], }, }, }, { type: "function", function: { name: "link_user_resource", description: "Link or unlink a user to a resource. Admin role required. Always confirm first.", parameters: { type: "object", properties: { userId: { type: "string", description: "User ID." }, resourceId: { type: ["string", "null"], description: "Resource ID or null to unlink." }, }, required: ["userId"], }, }, }, { type: "function", function: { name: "auto_link_users_by_email", description: "Auto-link all users without a resource to matching resources by email. Admin role required. Always confirm first.", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "set_user_permissions", description: "Set explicit permission overrides for a user. Admin role required. Always confirm first.", parameters: { type: "object", properties: { userId: { type: "string", description: "User ID." }, overrides: { type: ["object", "null"], properties: { granted: { type: "array", items: { type: "string" } }, denied: { type: "array", items: { type: "string" } }, chapterIds: { type: "array", items: { type: "string" } }, }, description: "Permission override object or null to clear.", }, }, required: ["userId"], }, }, }, { type: "function", function: { name: "reset_user_permissions", description: "Reset a user's permission overrides back to role defaults. Admin role required. Always confirm first.", parameters: { type: "object", properties: { userId: { type: "string", description: "User ID." }, }, required: ["userId"], }, }, }, { type: "function", function: { name: "get_effective_user_permissions", description: "Get a user's resolved permissions, role, and explicit overrides. Admin role required.", parameters: { type: "object", properties: { userId: { type: "string", description: "User ID." }, }, required: ["userId"], }, }, }, { type: "function", function: { name: "disable_user_totp", description: "Disable MFA TOTP for a user as an admin override. Admin role required. Always confirm first.", parameters: { type: "object", properties: { userId: { type: "string", description: "User ID." }, }, required: ["userId"], }, }, }, ], { list_users: { allowedSystemRoles: [SystemRole.ADMIN], }, create_user: { allowedSystemRoles: [SystemRole.ADMIN], }, set_user_password: { allowedSystemRoles: [SystemRole.ADMIN], }, update_user_role: { allowedSystemRoles: [SystemRole.ADMIN], }, update_user_name: { allowedSystemRoles: [SystemRole.ADMIN], }, link_user_resource: { allowedSystemRoles: [SystemRole.ADMIN], }, auto_link_users_by_email: { allowedSystemRoles: [SystemRole.ADMIN], }, set_user_permissions: { allowedSystemRoles: [SystemRole.ADMIN], }, reset_user_permissions: { allowedSystemRoles: [SystemRole.ADMIN], }, get_effective_user_permissions: { allowedSystemRoles: [SystemRole.ADMIN], }, disable_user_totp: { allowedSystemRoles: [SystemRole.ADMIN], }, }); export function createUserAdminExecutors( deps: UserAdminDeps, ): Record { return { async list_users(params: { limit?: number }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); const users = await caller.list(); return users.slice(0, Math.min(params.limit ?? 50, 100)); }, async create_user(params: { email: string; name: string; systemRole?: SystemRole; password: string; }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); let user; try { user = await caller.create({ email: params.email, name: params.name, password: params.password, ...(params.systemRole !== undefined ? { systemRole: params.systemRole } : {}), }); } catch (error) { const mapped = deps.toAssistantUserMutationError(error, "create"); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["user", "resource"], success: true, user, userId: user.id, message: `Created user ${user.name}.`, }; }, async set_user_password(params: { userId: string; password: string }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); let result; try { result = await caller.setPassword(params); } catch (error) { const mapped = deps.toAssistantUserMutationError(error, "password"); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["user"], ...result, message: `Reset password for user ${params.userId}.`, }; }, async update_user_role(params: { id: string; systemRole: SystemRole }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); let user; try { user = await caller.updateRole(params); } catch (error) { const mapped = deps.toAssistantUserMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["user"], success: true, user, userId: user.id, message: `Updated role for ${user.name} to ${user.systemRole}.`, }; }, async update_user_name(params: { id: string; name: string }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); let user; try { user = await caller.updateName(params); } catch (error) { const mapped = deps.toAssistantUserMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["user"], success: true, user, userId: user.id, message: `Updated user name to ${user.name}.`, }; }, async link_user_resource(params: { userId: string; resourceId?: string | null }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); let result; try { result = await caller.linkResource({ userId: params.userId, resourceId: params.resourceId ?? null, }); } catch (error) { const mapped = deps.toAssistantUserResourceLinkError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["user", "resource"], ...result, message: params.resourceId ? "Linked user to resource." : "Unlinked user resource.", }; }, async auto_link_users_by_email(_params: Record, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); const result = await caller.autoLinkAllByEmail(); return { __action: "invalidate" as const, scope: ["user", "resource"], success: true, ...result, message: `Auto-linked ${result.linked} user(s) by email.`, }; }, async set_user_permissions(params: { userId: string; overrides?: { granted?: string[]; denied?: string[]; chapterIds?: string[]; } | null; }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); let user; try { user = await caller.setPermissions({ userId: params.userId, overrides: params.overrides ?? null, }); } catch (error) { const mapped = deps.toAssistantUserMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["user"], success: true, user, userId: user.id, message: params.overrides ? "Updated user permission overrides." : "Cleared user permission overrides.", }; }, async reset_user_permissions(params: { userId: string }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); let user; try { user = await caller.resetPermissions(params); } catch (error) { const mapped = deps.toAssistantUserMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["user"], success: true, user, userId: user.id, message: "Reset user permissions to role defaults.", }; }, async get_effective_user_permissions(params: { userId: string }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); try { return await caller.getEffectivePermissions(params); } catch (error) { const mapped = deps.toAssistantUserMutationError(error); if (mapped) { return mapped; } throw error; } }, async disable_user_totp(params: { userId: string }, ctx: ToolContext) { const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx)); let result; try { result = await caller.disableTotp(params); } catch (error) { const mapped = deps.toAssistantUserMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["user"], success: true, ...result, message: `Disabled TOTP for user ${params.userId}.`, }; }, }; }