import type { AgentMessageSender } from "@mono-agent/agent-contracts"; import type { SlackMessageStreamLogger } from "./message-stream.js"; import type { SlackWebApi } from "./types.js"; /** Entries retained before the oldest is evicted. */ export declare const SLACK_USER_DIRECTORY_MAX_ENTRIES = 500; /** How long a resolved name is reused. Display names change rarely. */ export declare const SLACK_USER_DIRECTORY_TTL_MS: number; /** How long an unresolvable id is remembered, so one bad id is not retried per turn. */ export declare const SLACK_USER_DIRECTORY_NEGATIVE_TTL_MS: number; /** Concurrent `users.info` calls. Keeps a cold thread well inside the Tier 4 budget. */ export declare const SLACK_USER_DIRECTORY_LOOKUP_CONCURRENCY = 3; export interface SlackUserDirectoryOptions { /** Only `usersInfo` is used, and it is optional: an omitting client latches the directory off. */ api: Pick; logger?: SlackMessageStreamLogger; maxEntries?: number; ttlMs?: number; negativeTtlMs?: number; /** Injectable clock so TTL behaviour is testable without fake timers. */ now?: () => number; } /** * Bounded `users.info` cache that turns Slack user ids into model-visible * {@link AgentMessageSender} names. * * Three invariants make it safe to call on every turn: * * - It NEVER rejects. A speaker name is a nicety; failing a turn over one would * be absurd, so every failure degrades to an unnamed speaker. * - It NEVER exposes an id as a name. A Slack user id doubles as a DM channel id, * so the contract treats it as an exfiltration token rather than an identity. * A profile with no usable name resolves to nothing. * - It latches OFF permanently on `missing_scope`. Without that, a mis-scoped app * pays one guaranteed-failing call per speaker per turn, forever. * * Eviction is insertion-order (FIFO), not LRU: with a 500-entry budget against a * workspace's active speakers, the extra bookkeeping buys nothing. */ export declare class SlackUserDirectory { private readonly api; private readonly logger; private readonly maxEntries; private readonly ttlMs; private readonly negativeTtlMs; private readonly now; private readonly cache; private latchedOff; private loggedUnavailable; constructor(options: SlackUserDirectoryOptions); /** True once `users.info` is known to be unusable: absent method or missing scope. */ get unavailable(): boolean; /** * Resolve as many of `userIds` as the cache and `maxLookups` allow. Deduped, * concurrency-bounded, and abort-aware. Ids that cannot be named are simply * absent from the returned map. */ resolveMany(userIds: readonly string[], signal: AbortSignal | undefined, maxLookups: number): Promise>; /** One lookup. Caches both outcomes and never throws. */ private lookup; private read; private write; private noteUnavailable; } //# sourceMappingURL=user-directory.d.ts.map