import type { AgentSurface } 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_CHANNEL_DIRECTORY_MAX_ENTRIES = 200; /** How long a resolved surface is reused. Channel names change rarely. */ export declare const SLACK_CHANNEL_DIRECTORY_TTL_MS: number; /** How long an unresolvable id is remembered, so one bad id is not retried per turn. */ export declare const SLACK_CHANNEL_DIRECTORY_NEGATIVE_TTL_MS: number; export interface SlackChannelDirectoryOptions { /** Only `conversationsInfo` 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; } /** What `conversations.info` adds on top of what an event already told us. */ export interface SlackResolvedChannel { /** Channel name without a leading `#`. Absent for a DM, which has no name. */ readonly name?: string; /** Authoritative kind, which an `app_mention` event cannot supply on its own. */ readonly kind?: AgentSurface["kind"]; } /** * Bounded `conversations.info` cache that names the surface a turn is on. * * The same three invariants that make {@link SlackUserDirectory} safe to call on * every turn apply here, for the same reasons: * * - It NEVER rejects. A surface name is a nicety — the kind alone already tells * the agent whether it is in a DM or a shared channel — so every failure * degrades to an unnamed surface rather than failing the turn. * - It NEVER invents a name. A channel with no usable `name` resolves to nothing * rather than to its id: the id is carried separately and stating it twice, * once dressed as a name, would only mislead. * - It latches OFF permanently on `missing_scope`. Naming a surface needs * `channels:read`/`groups:read`/`im:read` beyond what posting needs, so a * mis-scoped app is the expected case, not an exotic one — without the latch it * would pay one guaranteed-failing call per turn, forever. * * Eviction is insertion-order (FIFO), not LRU: an agent talks in a handful of * channels, so the extra bookkeeping buys nothing against a 200-entry budget. */ export declare class SlackChannelDirectory { 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: SlackChannelDirectoryOptions); /** True once `conversations.info` is known to be unusable: absent method or missing scope. */ get unavailable(): boolean; /** * Resolve one channel's name and kind. Returns `undefined` — never throws — * when the channel cannot be described, which leaves the caller with whatever * the event itself already implied. */ resolve(channelId: string, signal: AbortSignal | undefined): Promise; private read; private write; private noteUnavailable; } //# sourceMappingURL=channel-directory.d.ts.map