Ctrl K
ComunidadGuías…

server/ask-routing.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// 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>;