server/ask-routing.ts
Rama main · réplica de lectura del repositorio original.
// Whose inbox a bot's own asks reach on a Team Cloud. An ask is a question or approval card, a sign-in request, a
// failure, or a routine's result. In a member's private chat it is that member's, always: private means private.
// Anywhere else (the bot's shared chats, a group chat, a routine the team made) it is the bot's owner's, or the Admins'
// when nobody owns it. Everyone who sees the conversation still sees the card there; this decides only whose Needs you,
// brief and notifications it reaches. Off a team nothing is routed.
import { botOwner, type OwnedBot } from "../shared/bot-owner.ts";
import type { NotifyKind } from "../shared/notification.ts";
import { audienceMember, type Audience } from "./audience.ts";
/** The notifications that are a bot asking a person for something; "done", "mention" and the rest reach whoever sees
* the conversation, unless one names its person (`forPerson`). */
export const ASK_NOTIFICATIONS: ReadonlySet<NotifyKind> = new Set([
"approval", "question", "sign-in", "secret", "routine-failed", "routine-deferred", "turn-failed", "incident",
]);
export interface AskViewer {
/** The viewer's person key; null for a session that names nobody. */
person: string | null;
admin: boolean;
}
export interface AskRoutingDeps {
team: boolean;
audienceOf(threadId: string): Audience;
bot(botId: string): OwnedBot | undefined;
personKeyOf(email: string): string;
/** A member the Admin said left: their bots' asks go to the Admins until someone else owns them. */
gone?(email: string): boolean;
}
export function createAskRouting(deps: AskRoutingDeps) {
/** The person key an ask in this conversation is for; null: the Admins. */
function askedPerson(botId: string | undefined, threadId: string): string | null {
const member = audienceMember(deps.audienceOf(threadId));
if (member) return member;
const bot = botId ? deps.bot(botId) : undefined;
const owner = bot ? botOwner(bot) : null;
return owner && !deps.gone?.(owner) ? deps.personKeyOf(owner) : null;
}
/** Whether an ask in this conversation is the viewer's. Off a team every ask is everyone's who sees it. */
function asks(viewer: AskViewer, botId: string | undefined, threadId: string): boolean {
if (!deps.team) return true;
const person = askedPerson(botId, threadId);
return person === null ? viewer.admin : person === viewer.person;
}
/** Whether a notification reaches this viewer: one for a person only them, an ask only its person, anything else
* whoever sees it. */
function notificationReaches(viewer: AskViewer, notification: { kind?: unknown; botId?: unknown; threadId?: unknown; forPerson?: unknown }): boolean {
if (deps.team && typeof notification.forPerson === "string") return notification.forPerson === viewer.person;
if (!deps.team || typeof notification.threadId !== "string" || !ASK_NOTIFICATIONS.has(notification.kind as NotifyKind)) return true;
return asks(viewer, typeof notification.botId === "string" ? notification.botId : undefined, notification.threadId);
}
return { team: deps.team, askedPerson, asks, notificationReaches };
}
export type AskRouting = ReturnType<typeof createAskRouting>;