/** * People directory — the humans who use this Crewly instance, keyed by their * Slack user id, with a role (issue #968). * * - **owner**: the instance owner. Their person id is always `owner` — * what this backend stores and sends Cloud. Any Slack id known to be the * owner maps to it: the Slack installer (Cloud's workspace config, i.e. the * Cloud account's Slack identity), `SLACK_OWNER_USER_ID` (installs whose * Slack credentials come from env), and Slack ids the owner marked as their * own in Settings › People. Always shown, never removable. * - **member**: a person in the workspace. A Slack user who messages an agent * is added as a member automatically. * - **guest**: someone the owner marked as a guest. Guests are not included * when a connector is shared with "all members". * * Bots (the master bot, agent bots) are never people: they are not added, * and rows a bot left in `people.json` are removed when the file is loaded. * * Stored in `/people.json`. Writes are atomic. * * specs/per-person-access.md * * @module services/people/people-directory.service */ /** A person's role on this instance. */ export type PersonRole = 'owner' | 'member' | 'guest'; /** One person. */ export interface Person { /** Slack user id (`U0123…`), or `owner` for the instance owner */ id: string; /** The owner's known Slack user ids (owner row only, for display) */ slackUserIds?: string[]; /** Display name */ name?: string; role: PersonRole; /** `auto`: added when they first messaged an agent; `owner`: added or edited by the owner */ source: 'auto' | 'owner'; createdAt: string; updatedAt: string; } /** An invalid edit. */ export declare class PeopleDirectoryError extends Error { } /** Injectable dependencies. */ export interface PeopleDirectoryDeps { /** Store file (default `/people.json`) */ filePath?: string; /** The instance owner's Slack user id, when known (the Slack installer) */ getOwnerSlackUserId?: () => string | null | undefined; /** Whether a Slack user id is a bot (the master bot or an agent's bot) */ isBot?: (slackUserId: string) => boolean; /** Environment (default `process.env`), for `SLACK_OWNER_USER_ID` */ env?: NodeJS.ProcessEnv; now?: () => number; } /** * Whether a value is a valid role. * * @param value - Candidate * @returns True for owner / member / guest */ export declare function isPersonRole(value: unknown): value is PersonRole; /** * Whether a value is a person id this directory accepts: a Slack user id, or * the owner placeholder. * * @param value - Candidate * @returns True when valid */ export declare function isPersonId(value: unknown): value is string; /** The people directory. */ export declare class PeopleDirectoryService { private readonly filePathOverride?; private readonly installerSlackUserId; private readonly botCheck; private readonly env; private readonly now; /** * @param deps - Store path, owner and bot lookups, environment, clock */ constructor(deps?: PeopleDirectoryDeps); /** @returns Store path (resolved per call so CREWLY_HOME changes apply) */ private filePath; /** * The owner's person id. Always `owner` — what is stored and sent Cloud. * * @returns `owner` */ ownerId(): string; /** * Every Slack id known to be the owner: the Slack installer, * `SLACK_OWNER_USER_ID`, and ids the owner marked as their own. * * @returns Slack user ids (deduplicated, maybe empty) */ ownerSlackUserIds(): string[]; /** * Whether a person id is the instance owner. * * @param id - Person id * @returns True for `owner` or any Slack id known to be the owner */ isOwner(id: string | null | undefined): boolean; /** * The id to store and send for a person: `owner` for the owner (by either * spelling), else the id as given. * * @param id - Person id * @returns Canonical person id */ canonicalId(id: string): string; /** * Whether a Slack user id is a bot (never a person). * * @param id - Slack user id * @returns True for the master bot or an agent's bot */ isBot(id: string | null | undefined): boolean; /** * Everyone, owner first, then by name. * * @returns People (copies) */ list(): Person[]; /** * One person. * * @param id - Person id * @returns The person, or null when unknown */ get(id: string): Person | null; /** * A person's role. Unknown people are members (the default for a Slack * user in the workspace). * * @param id - Person id * @returns Role */ roleOf(id: string): PersonRole; /** * How to name a person to others. * * @param id - Person id * @returns Their name, "the owner" for an owner without one, else the id */ displayName(id: string): string; /** * Record a Slack user who messaged an agent (added as a member the first * time; a missing name is filled in). Never changes a role. * * @param id - Slack user id * @param name - Their display name, when known */ noteSeen(id: string, name?: string): void; /** * Add or edit a person (owner action). Giving a Slack id the `owner` role * marks it as the owner's own Slack account (for installs that cannot * tell, e.g. Slack credentials from env): it then maps to `owner`. * * @param id - Slack user id * @param patch - Name and/or role * @returns The person * @throws PeopleDirectoryError on an invalid id or role, a bot, or when changing the owner's role */ upsert(id: string, patch: { name?: unknown; role?: unknown; }): Person; /** * Remove a person (they come back as a member if they message an agent again). * * @param id - Person id * @returns True when removed * @throws PeopleDirectoryError for the owner */ remove(id: string): boolean; /** * Record a Slack id as the owner's own and drop any member row it had. * * @param id - Slack user id */ private markOwnerSlackId; /** * The stored file, without bots. Bot rows found on disk (written by an * earlier version that recorded agent posts as people) are removed from * the file here (best effort). * * @returns The stored file (empty when missing or unreadable) */ private read; /** @returns The stored file as on disk (valid rows only; empty when missing or unreadable) */ private readRaw; /** * Write atomically. * * @param file - Contents */ private write; } /** * Trim and bound a display name. * * @param name - Raw name * @returns Clean name, or undefined when empty */ export declare function cleanPersonName(name: string | null | undefined): string | undefined; /** * Record a Slack user who messaged an agent, with their name (best effort, * never throws). * * @param slackUserId - Slack user id * @param name - Display name, when known */ export declare function notePerson(slackUserId: string, name?: string | null): void; /** * Tell the directory how to find the owner's Slack id (wired once Slack is up). * * @param lookup - Lookup, or null */ export declare function setPeopleOwnerLookup(lookup: (() => string | null | undefined) | null): void; /** * Tell the directory how to recognise Crewly's bots (wired once Slack is up). * * @param lookup - Whether a Slack user id is a bot, or null */ export declare function setPeopleBotLookup(lookup: ((slackUserId: string) => boolean) | null): void; /** * The backend's people directory. * * @returns Singleton */ export declare function getPeopleDirectory(): PeopleDirectoryService; /** * Replace the singleton (tests). * * @param service - Instance, or null to reset */ export declare function setPeopleDirectoryForTesting(service: PeopleDirectoryService | null): void; //# sourceMappingURL=people-directory.service.d.ts.map