Ctrl K
ComunidadGuías…

server/audience.ts

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

Archivo original ↗Preguntar a KiBot
// The audience rule of a Team Cloud (docs/team-cloud.md). Everything stored carries an audience: `team` (everyone),
// `room:<id>` (whoever sees the room) or `member:<person>` (that person only: an Admin is no exception). A turn reads
// only inputs whose audience includes everyone who will see its answer, and writes only into its own audience. On a My
// Cloud one person uses everything, so every audience there is the team's and nothing here changes what that Cloud does.
export type Audience = "team" | `room:${string}` | `member:${string}`;

export const TEAM_AUDIENCE: Audience = "team";
const ID = /^[\w-]{1,200}$/;

export function roomAudience(groupId: string): Audience {
  return `room:${groupId}`;
}

export function memberAudience(person: string): Audience {
  return `member:${person}`;
}

export function parseAudience(value: unknown): Audience | null {
  if (value === "team") return "team";
  if (typeof value !== "string") return null;
  const match = /^(room|member):([\w-]{1,200})$/.exec(value);
  return match ? (value as Audience) : null;
}

export function audienceRoom(audience: Audience): string | null {
  return audience.startsWith("room:") ? audience.slice("room:".length) : null;
}

export function audienceMember(audience: Audience): string | null {
  return audience.startsWith("member:") ? audience.slice("member:".length) : null;
}

/** The read rule: an input reaches a turn only when everyone who sees the turn's answer may see the input. The team's
 * things reach every turn; a room's only that room's turns; a member's only that member's own. */
export function mayRead(input: Audience, turn: Audience): boolean {
  return input === "team" || input === turn;
}

/** The write rule: a turn writes into its own audience, never a wider or another one. */
export function writesInto(turn: Audience): Audience {
  return turn;
}

export interface AudienceViewer {
  admin: boolean;
  /** The viewer's person key; null for a session that has none. */
  person: string | null;
  seesRoom(groupId: string): boolean;
}

/** Whether one person may see something of this audience: the team's, the rooms they see (an Admin every room), and
 * their own. A person's private chat is theirs alone: no role, an Admin's or the Owner's, sees another person's. */
export function audienceIncludes(audience: Audience, viewer: AudienceViewer): boolean {
  if (audience === "team") return true;
  const room = audienceRoom(audience);
  if (room !== null) return viewer.admin || viewer.seesRoom(room);
  const person = audienceMember(audience);
  return person !== null && viewer.person !== null && person === viewer.person;
}

/** Where a bot's memory for an audience lives: the bot's own folder is the team's layer; a room's and a member's layers
 * are sibling folders (never inside the bot's folder, which every turn of that bot can read). */
export function layerId(botId: string, audience: Audience): string {
  if (audience === "team") return botId;
  const room = audienceRoom(audience);
  if (room !== null) return `${botId}--room-${room}`;
  return `${botId}--member-${audienceMember(audience)}`;
}

const LAYER = /^([\w-]+?)--(room|member)-([\w-]+)$/;

export function isLayerId(id: string): boolean {
  return LAYER.test(id);
}

/** The bot a layer belongs to; a bot's own id as it is. */
export function baseBotId(id: string): string {
  const match = LAYER.exec(id);
  return match ? match[1]! : id;
}

export function layerAudience(id: string): Audience {
  const match = LAYER.exec(id);
  if (!match) return "team";
  return match[2] === "room" ? roomAudience(match[3]!) : memberAudience(match[3]!);
}

/** A conversation's audience from where it is and who opened it: a room's is the room's; a person's conversation with a
 * bot is theirs; the bot's main chat, and a conversation nobody or a marker (nobody, outside) opened, is the team's. */
export function threadAudienceFrom(input: { room: string | null | undefined; starter: string | undefined; markers: ReadonlySet<string> }): Audience {
  if (input.room) return roomAudience(input.room);
  const starter = input.starter;
  if (!starter || !ID.test(starter) || input.markers.has(starter)) return "team";
  return memberAudience(starter);
}