import { type AgentAttachment, type ChannelAskSnapshot, type ChannelAskSubmission, type ChannelAskSubmissionResult, type AgentContinuationTurn, type AgentMessageSender, type AgentPrecedingMessage, type AgentRequestBase, type AgentResponder as SharedAgentResponder, type AgentResponse, type ProcessJobProjection } from "@mono-agent/agent-contracts"; import { type AgentMessageStream, type SlackMessageStreamLogger } from "./message-stream.js"; import type { SlackRuntimeControls, SlackRuntimeSlashCommands } from "./runtime-controls.js"; export type { SlackRuntimeControls, SlackRuntimeEffortOption, SlackRuntimeModelOption, SlackRuntimeSlashCommands, } from "./runtime-controls.js"; import type { SlackThreadContextOptions } from "./thread-context.js"; import type { SlackBlockActionsPayload, SlackChannelId, SlackEventCallback, SlackInteractivityPayload, SlackMessageTs, SlackSlashCommandPayload, SlackShortcutPayload, SlackUserId, SlackWebApi } from "./types.js"; export type SlackTriggerKind = "direct" | "app_mention"; export interface AgentRequest extends AgentRequestBase { conversationId: string; channelId: SlackChannelId; messageTs: SlackMessageTs; threadTs: SlackMessageTs; eventId: string; teamId?: string; userId?: SlackUserId; text: string; trigger: SlackTriggerKind; abortSignal: AbortSignal; attachments?: readonly AgentAttachment[]; /** Model-visible speaker identity; `sender.id` stays host-only. */ sender?: AgentMessageSender; /** Model-visible background transcript of the conversation before this turn. */ precedingMessages?: readonly AgentPrecedingMessage[]; metadata: { slack: SlackRequestMetadata; [key: string]: unknown; }; } /** Tunes how inbound Slack file attachments are downloaded. */ export interface SlackAttachmentOptions { /** * Maximum decoded bytes to accept per file. Files whose advertised size * exceeds this — or whose download exceeds it — are skipped. Default 10 MiB. */ maxBytes?: number; /** * Allowed file mimetypes. A file whose mimetype is not listed is skipped. * Defaults to a conservative allowlist of common image and document types. */ allowedMimeTypes?: readonly string[]; } export interface SlackRequestMetadata { teamId?: string; apiAppId?: string; eventId: string; eventTime?: number; channel: { id: SlackChannelId; type?: string; }; message: { ts: SlackMessageTs; threadTs?: SlackMessageTs; eventTs?: SlackMessageTs; }; user?: { id: SlackUserId; }; trigger: SlackTriggerKind; /** Request-scoped runtime override selected through Slack's native controls. */ model?: string; /** Request-scoped effort override selected through Slack's native controls. */ effort?: string; } export type { AgentResponse }; export type AgentResponder = SharedAgentResponder; /** * Outcome of a proactive {@link SlackAdapter.notify} delivery. `delivered` is * true only when the answer reached the channel; otherwise `reason` carries a * short, human-readable cause for the silent drop (e.g. concurrency cap, the * agent produced no answer, a cancelled or failed run). */ export interface SlackNotifyResult { readonly delivered: boolean; readonly reason?: string; /** Stable machine-readable outcome for durable continuation routing. */ readonly code?: string; readonly retryable?: boolean; /** True when Slack may have accepted the post but no receipt was observed. */ readonly ambiguous?: boolean; readonly deliveryId?: string; readonly channelId?: "slack"; /** Whether the confirmed Slack post was also appended to durable conversation history. */ readonly historyRecorded?: boolean; /** Bounded machine code when a confirmed post could not be recorded to history. */ readonly historyErrorCode?: string; /** Whether a process-job completion joined an active turn or ran after it. */ readonly disposition?: "steered" | "follow_up"; } /** * Options for {@link SlackAdapter.notify}. With `verbatim`, `text` is posted to * the destination UNCHANGED with no model call (native cron/webhook notification * — the producing run already wrote the message) and recorded to history so a * reply resumes with it in context. Without it, `text` is run as a turn. */ export interface SlackNotifyOptions { readonly verbatim?: boolean; /** Stable host delivery identity. Converted to Slack's UUID client_msg_id. */ readonly deliveryKey?: string; /** Prefer the active turn while retaining this notification's fallback queue slot. */ readonly steerActive?: boolean; /** * Request notification-suppressed delivery for cross-channel parity. * * Slack's `chat.postMessage` contract has no bot-controlled equivalent to * Telegram's `disable_notification`. The adapter therefore posts normally * and, when a logger is configured, emits an explicit warning instead of * forwarding an invented field or claiming that Slack client/workspace * notifications were suppressed. */ readonly silent?: boolean; } export interface SlackContinuationSynthesisInput { /** Exact history identity, including a rollover bucket when present. */ readonly conversationId: string; readonly replyToConversationId: string; readonly channelId: SlackChannelId; readonly threadTs?: SlackMessageTs; readonly prompt: string; readonly continuation: AgentContinuationTurn; } export interface SlackAdapterMessages { welcomeText?: string; helpText?: string; busyText?: string; unauthorizedText?: string; cancelledText?: string; errorText?: string; unsupportedText?: string; /** * Prompt substituted when someone mentions the app with no other text. Unlike * every other member here this is INBOUND — it becomes the turn's user message, * not a canned reply — because a bare mention is a summons into a conversation * that already carries the question. */ bareMentionPrompt?: string; } export interface SlackAdapterStreamOptions { initialStatusText?: string; editDebounceMs?: number; maxMessageChars?: number; maxSendRetries?: number; retryCapMs?: number; retryBaseDelayMs?: number; showHints?: boolean; /** * Deliver only the final answer with a 👀 "seen" reaction while working, * instead of streaming interim edits. Defaults to true for the Slack adapter. */ finalOnly?: boolean; } export interface SlackAdapterLogger extends SlackMessageStreamLogger { debug?(message: string, metadata?: Record): void; info?(message: string, metadata?: Record): void; } /** * Binds a Slack shortcut `callback_id` to a prompt. When a user invokes that * shortcut, the adapter runs the prompt as a proactive turn — the same machinery * as a cron/webhook nudge — making the shortcut a persistent one-click trigger * for an agent routine. When `channelId` is omitted, a MESSAGE shortcut uses its * source channel and a GLOBAL shortcut uses the first allowlisted channel; a * global shortcut needs `channelId` only when no allowlist default exists. */ export interface SlackShortcutBinding { readonly callbackId: string; readonly prompt: string; /** * Destination channel for the run's reply. When omitted, a MESSAGE shortcut * uses its source channel and is refused if that source is unauthorized; it * does not retry an allowlist default. A source-less GLOBAL shortcut uses the * first explicit `allowedChannelIds` entry. With `allowAllChannels` and no * explicit allowlist, a global shortcut needs a `channelId`. Pin it to bound * the destination. */ readonly channelId?: SlackChannelId; /** * Optional message posted immediately when the shortcut is invoked, before the * run starts — instant feedback for an action whose result lands seconds later * (e.g. "🔄 Syncing…"). Best-effort: a failed ack post does not block the run. */ readonly ackText?: string; /** * When true (and `ackText` is set), the run's result posts as a threaded reply * under the instant ack instead of as a separate top-level message. Default off. */ readonly threadReply?: boolean; } /** * Outcome of routing an interaction (a shortcut or a Home-tab button). `id` is * the shortcut's `callback_id` or the button's `action_id`. `triggered` means a * bound interaction ran (`delivered` mirrors the proactive turn's outcome); the * other kinds explain why nothing ran, for logging. */ export type SlackInteractionHandlingResult = { kind: "triggered"; id: string; channelId: SlackChannelId; delivered: boolean; reason?: string; } | { kind: "ignored"; reason: "no_action" | "unbound" | "missing_channel"; id?: string; } | { kind: "unauthorized"; id: string; channelId: SlackChannelId; } | { kind: "runtime_control"; id: string; channelId: SlackChannelId; control: "model" | "effort"; outcome: "updated" | "cancelled" | "expired" | "already_recorded"; } | { kind: "ask"; id: string; channelId: SlackChannelId; outcome: "answered" | "selection_updated" | "custom_requested" | "expired"; }; /** Outcome of routing a workspace-registered model/effort slash command. */ export type SlackSlashCommandHandlingResult = { kind: "runtime_command"; command: string; channelId: SlackChannelId; control: "model" | "effort"; } | { kind: "ignored"; reason: "malformed" | "unbound"; command?: string; } | { kind: "unauthorized"; command: string; channelId: SlackChannelId; }; /** * A button rendered on the App Home tab. Clicking it runs `prompt` as a proactive * turn (same machinery as a shortcut), replying in `channelId` (the Home tab * carries no channel of its own, so this — or the first allowlisted channel — is * where the result lands). `label` is the button text; `ackText` posts instantly. */ export interface SlackHomeButton { readonly actionId: string; readonly label: string; readonly prompt: string; readonly channelId?: SlackChannelId; readonly ackText?: string; /** * When true (and `ackText` is set), the run's result posts as a threaded reply * under the instant ack — one ack+result thread instead of two top-level * messages. Default: top-level. See {@link SlackHomeButton.ackText}. */ readonly threadReply?: boolean; } /** App Home tab options: whether to publish it, an optional header, and its buttons. */ export interface SlackHomeTabOptions { readonly enabled: boolean; readonly headerText?: string; readonly buttons: readonly SlackHomeButton[]; } export interface SlackAdapterOptions { api: SlackWebApi; responder: AgentResponder; allowedChannelIds?: SlackChannelId[]; allowAllChannels?: boolean; botUserIds?: SlackUserId[]; mentionTextAliases?: string[]; stripMentionText?: boolean; /** Authenticated Slack username used for one readable self-address marker. */ botUserName?: string; stream?: SlackAdapterStreamOptions; messages?: SlackAdapterMessages; attachments?: SlackAttachmentOptions; /** Native `/model` and `/effort` choices. Omit to leave those commands unbound. */ runtimeControls?: SlackRuntimeControls; /** * Exact workspace-registered slash commands for the native runtime controls. * `startSlackAdapter` derives these from the authenticated bot username (for * example `/mickey-model` and `/mickey-effort`) unless explicitly overridden. */ runtimeSlashCommands?: SlackRuntimeSlashCommands; /** * Shortcut bindings (callback_id → prompt). Invoking a bound shortcut in/for an * authorized channel runs its prompt as a proactive turn. Omitted/empty means * no shortcuts are wired (interactions are ignored). */ shortcuts?: readonly SlackShortcutBinding[]; /** * App Home tab configuration. When `enabled`, the adapter publishes a persistent * panel of action buttons whenever a user opens the Home tab, and a button click * runs its bound prompt as a proactive turn. Omitted/disabled means no Home tab. */ homeTab?: SlackHomeTabOptions; /** * Resolve the speaker's real name via `users.info` (needs the `users:read` * scope) so the harness can label the turn with who is talking instead of * nothing. Default `true`; degrades to an unnamed speaker when the scope or the * client method is missing, which is byte-identical to the pre-name behaviour. */ resolveUserNames?: boolean; /** * Resolve the surface's name via `conversations.info` (needs `channels:read` / * `groups:read`) so the agent can say WHICH channel it is talking in rather * than only that it is in one. Default `true`; degrades to the surface kind * and id alone when the scope or the client method is missing. */ resolveChannelNames?: boolean; /** * Best-effort model-visible transcript of what was said in the conversation * before the agent was triggered. Enabled by default; needs a `*:history` scope. */ threadContext?: SlackThreadContextOptions; /** * This app's own `bot_id` from `auth.test`, so its own posts are recognized when * reading a conversation back. `botUserIds` cannot cover messages Slack * attributes to the app rather than to a user. */ botId?: string; logger?: SlackAdapterLogger; /** * Resolve an in-thread reply back to the conversation that produced the message * it threads off. When set and an inbound threaded reply's `(channel, threadTs)` * matches a recorded post, the run continues that producing conversation instead * of a fresh `slack::` (which would have no history). Injected * by the host so this package stays free of the artifact store. */ resolvePostIndex?: (channelId: string, ts: string) => Promise; /** * Record that this adapter posted a message at `(channel, ts)` for conversation * `conversationId`, so a later in-thread reply can be resolved back to it. Used * for top-level proactive posts (a fresh thread root with no prior history). * Fire-and-forget; best-effort. */ recordPostedMessage?: (channelId: string, ts: string, conversationId: string) => void; /** Host-owned structured AskUser state, consumed before message admission. */ pendingAsks?: SlackPendingAsks; } export interface SlackPendingAsks { getPendingAsk(conversationId: string): ChannelAskSnapshot | undefined | Promise; submitAskAnswers(input: ChannelAskSubmission): ChannelAskSubmissionResult | Promise; cancel(conversationId: string): void; } export type SlackEventIgnoredReason = "unsupported_event" | "unsupported_message" | "no_usable_attachments" | "from_bot" | "from_self"; export type SlackEventHandlingResult = { kind: "handled"; eventId: string; channelId: SlackChannelId; action: "command" | "responded"; command?: "start" | "help" | "model" | "effort"; trigger: SlackTriggerKind; metadata?: Record; } | { kind: "ignored"; reason: SlackEventIgnoredReason; eventId?: string; channelId?: SlackChannelId; } | { kind: "unauthorized"; eventId: string; channelId: SlackChannelId; } | { kind: "busy"; eventId: string; channelId: SlackChannelId; } | { kind: "cancelled"; eventId: string; channelId: SlackChannelId; } | { kind: "error"; eventId: string; channelId?: SlackChannelId; error: unknown; } | { kind: "home_published"; eventId: string; userId: SlackUserId; }; /** * Thrown synchronously by {@link SerialQueue.run} when the queue is already at * its depth cap. The adapter catches this sentinel to answer with the busy * terminal instead of admitting an unbounded backlog. */ export declare class SerialQueueFullError extends Error { readonly code: "serial_queue_full"; constructor(maxDepth: number); } /** * Minimal per-conversation serial queue: each submitted task runs only after the * previous one settles, preserving arrival order. A task's failure does not * poison the queue (the chain swallows it; the caller still sees the rejection). * * The queue is bounded by {@link maxDepth}: once `depth` reaches the cap, `run` * rejects synchronously with a {@link SerialQueueFullError} BEFORE incrementing * or chaining, so an over-cap task never enters the chain (mirroring the harness * LiveSessionManager's maxPendingPerConversation rejection). */ export declare class SerialQueue { private tail; private depth; private readonly maxDepth; constructor(maxDepth?: number); run(task: () => Promise): Promise; /** True when no task is queued or running. */ get idle(): boolean; get full(): boolean; } export declare class SlackAdapter { private readonly api; private readonly responder; private readonly allowAllChannels; private readonly allowedChannelIds; private readonly botUserIds; private readonly rawBotUserIds; private readonly mentionTextAliases; private readonly stripMentionText; private readonly botUserName; private readonly streamOptions; private readonly messages; private readonly attachmentMaxBytes; private readonly allowedMimeTypes; /** callback_id → shortcut binding for registered Slack shortcuts. */ private readonly shortcuts; /** action_id → button binding for App Home tab buttons. */ private readonly homeButtons; /** Ordered Home tab buttons (for rendering the view in config order). */ private readonly homeButtonOrder; private readonly homeTabEnabled; private readonly homeTabHeaderText; private readonly runtimeCatalog; /** Exact registered slash command name -> runtime control. */ private readonly runtimeSlashCommands; /** DM choices are DM-wide; shared-channel choices may be channel-wide or thread-local. */ private readonly runtimeSelections; /** Active interactive menu contexts, keyed by the bot menu message. */ private readonly runtimeMenus; /** One active menu per control/scope; opening a newer menu expires the older one. */ private readonly activeRuntimeMenus; /** Bounded replay guard for already-consumed interactive messages. */ private readonly answeredRuntimeMenus; /** First allowlisted channel (original case), used as a global interaction's default reply destination. */ private readonly defaultShortcutChannelId; private readonly logger; private readonly replyFiles; private readonly resolvePostIndex; private readonly recordPostedMessage; private readonly pendingAsks; private readonly askPresentations; /** * In-flight abort controllers per thread. The harness serializes runs for a * conversation, so several may be queued/active concurrently; /cancel aborts * every controller for the thread (and clears the harness queue via * responder.cancel). */ private readonly activeControllers; /** The same controllers indexed by resolved conversation for cross-thread aliases. */ private readonly activeControllersByConversation; /** * The run key currently holding a conversation, for as long as its responder * call is in flight — the physical Slack thread for an inbound turn, or a * `proactive:` key for a top-level post that has no user thread at all. * * Two threads can still resolve to ONE conversationId — a threaded proactive * post, or a recorded alias — and serializing them there is correct, because * they share a session. Steering across them is not: it would fold a message * typed in one thread into a run streaming into another. */ private readonly activeRunKeyByConversation; /** * Per-conversation admission queue. Socket Mode dispatches envelopes * concurrently, and pre-submit work (status + file download) is variable * latency, so without this a later same-thread message could reach * responder.respond() (and the harness FIFO) before an earlier one. We * serialize respondToEvent per conversation to preserve message order. * /cancel stays out-of-band (handled before this queue). */ private readonly admissionQueues; /** Bounded exact-origin state and per-job tails for host-owned lifecycle cards. */ private readonly processJobLifecycles; private readonly processJobUpdateTails; /** Speaker-name resolver. Undefined when `resolveUserNames` is off. */ private readonly userDirectory; /** Surface-name resolver. Undefined when `resolveChannelNames` is off. */ private readonly channelDirectory; private readonly threadContext; /** This app's own `bot_id`, so its own posts never enter a transcript. */ private readonly ownBotId; /** * Per-channel rate-limit breaker. Slack allows non-Marketplace apps roughly one * `conversations.*` read per minute, so once throttled we skip the call outright * rather than spending the turn's deadline learning that again. */ private readonly contextRateLimitedUntil; /** Latched after a missing `*:history` scope: every later read would also fail. */ private contextScopeMissing; constructor(options: SlackAdapterOptions); presentAsk(channelId: SlackChannelId, threadTs: SlackMessageTs | undefined, snapshot: ChannelAskSnapshot): Promise; updateAsk(channelId: SlackChannelId, snapshot: ChannelAskSnapshot): Promise; handleEventCallback(callback: SlackEventCallback): Promise; /** * Deliver a proactive notification to a Slack destination by running it as a * turn on that destination's OWN harness (shared session/history + the same * per-conversation admission queue as inbound messages) and posting the answer * through the normal stream. `threadTs` targets an existing thread (clean * continuity — the user's in-thread replies share the session); omitting it * posts top-level (fire-and-forget: a fresh top-level post has no pre-existing * thread to share continuity with). Used by cron/webhook nudges. Best-effort: * a failed or empty turn posts nothing. */ notify(channelId: SlackChannelId, threadTs: SlackMessageTs | undefined, text: string, options?: SlackNotifyOptions): Promise; /** * Post or monotonically edit one host-owned process-job lifecycle message. * This never invokes the responder and refuses any origin/thread mismatch. */ updateProcessJob(channelId: SlackChannelId, threadTs: SlackMessageTs | undefined, projection: ProcessJobProjection): Promise; /** Test/health seam proving job-id message references remain bounded. */ processJobMessageRefCount(): number; /** Test/health seam proving all lifecycle identity state remains bounded. */ processJobLifecycleStateCount(): number; private updateProcessJobNow; private postProcessJobSurface; private reserveProcessJobLifecycle; private rememberProcessJobMessage; private rememberTerminalFallbackAttempt; /** * Run a framework-owned continuation synthesis in the original conversation * without posting or committing history. The harness enforces the immutable * history boundary and zero-tool policy carried by `continuation`; native * delivery happens only after the caller durably persists the returned text. */ synthesizeContinuation(input: SlackContinuationSynthesisInput): Promise; /** * Append an operator-confirmed continuation to durable history without * touching Slack. This is deliberately separate from notify(verbatim) so an * ambiguous native send can be reconciled without any chance of reposting. */ recordContinuationHistory(conversationId: string, text: string, deliveryKey?: string): Promise<{ readonly recorded: true; } | { readonly recorded: false; readonly code: string; }>; /** Route a workspace-registered runtime-control slash command. */ handleSlashCommand(payload: SlackSlashCommandPayload): Promise; private runtimeScopeFor; private runtimeScopeForSlashCommand; private runtimeCommandTargetForEvent; private localRuntimeSelectionFor; private inheritedRuntimeSelectionFor; private runtimeSelectionFor; private saveRuntimeSelection; private effectiveRuntimeModel; private defaultRuntimeModelFor; private selectRuntimeModel; private selectRuntimeEffort; private applyRuntimeSelection; private modelSelectionConfirmation; private effortSelectionConfirmation; private handleRuntimeModelCommand; private handleRuntimeEffortCommand; private modelMenuBlocks; private effortMenuBlocks; private postRuntimeCommandReply; private postRuntimeMenu; private rememberRuntimeMenu; private forgetRuntimeMenu; private rememberAnsweredRuntimeMenu; private replaceRuntimeMenuQuietly; private expireRuntimeMenu; private handleRuntimeBlockAction; /** Route shortcut, runtime-select, and App Home interaction payloads. */ handleInteraction(payload: SlackInteractivityPayload): Promise; /** * Route a Slack shortcut payload. When its `callback_id` is bound to a prompt * and the resolved destination channel is authorized, run that prompt as a * proactive turn. The destination is the binding's `channelId`, else the * payload's own channel (message shortcuts), else the first allowlisted channel. */ handleShortcut(payload: SlackShortcutPayload): Promise; /** * Route a Block Kit `block_actions` payload. Native runtime selectors are * handled first when configured; otherwise the first bound App Home button is * used. A Home-tab click carries no channel, so its reply goes to the button's * `channelId` (or the first allowlisted channel). */ handleBlockActions(payload: SlackBlockActionsPayload): Promise; private handleAskBlockAction; /** * Shared interaction run path: resolve the destination channel, enforce the * allowlist, post the optional instant ack, then run the bound prompt as a * proactive turn. The returned result is for logging. */ private runBoundInteraction; /** * Publish the App Home tab for a user when they open it. Best-effort: a publish * failure is logged, not thrown, so opening Home never surfaces an error. */ private handleAppHomeOpened; /** Build the App Home tab Block Kit: an optional header plus one button per configured Home button. */ private buildHomeTabBlocks; /** * Build the stream options shared by both proactive delivery paths * ({@link runProactiveTurn} and {@link runVerbatimDelivery}): a threaded post * targets the existing thread; a top-level post announces the thread root it * opens through `onThreadRootPosted`, and the caller decides which conversation * that thread belongs to so a user's in-thread reply resolves there. */ private buildProactiveStreamOptions; /** * Deliver `text` VERBATIM to a Slack destination: post it unchanged through the * normal stream with NO model call (the producing cron/webhook run already wrote * the message), then record it to durable history via the responder so a later * reply resumes with it in context. A top-level post records against the thread * it just opened rather than the destination, so each card is its own * conversation; a threaded post stays on the destination. Best-effort: a * history-record failure never fails an already-delivered post. */ private runVerbatimDelivery; private runProactiveTurn; /** * Model-visible context for one inbound turn: who is speaking, and what was * said in this conversation before the agent was pulled in. * * Strictly best-effort. It never throws, never retries, never paginates, and * never delays the turn beyond one bounded deadline covering the whole phase. A * missing scope, a rate limit, an unusable window, or a client without the read * methods all yield less context — never a failed or slower turn. The result * with nothing in it renders exactly as the adapter did before any of this * existed. */ private collectTurnContext; /** * One read of the surrounding conversation, or the reason there wasn't one. * * Exactly one request, ever. An in-thread trigger reads the thread; a top-level * trigger reads recent channel history. The replies path asks for a page * anchored at the trigger (`latest` + `inclusive`) so * {@link selectPrecedingSlackMessages} can verify the window rather than trust * Slack's undocumented truncation direction. */ private readConversationWindow; /** Turn a failed read into a skip reason, latching cooldowns as it goes. */ private classifyContextFailure; /** Names for the trigger's sender plus every distinct speaker in the window. */ private resolveSpeakerNames; /** * The conversation the run should continue. A genuine in-thread reply * (`threadTs !== messageTs`) whose `(channel, threadTs)` matches a message we * posted resolves to that producing conversation; everything else uses the * default `slack::`. Best-effort: a lookup error falls back. */ private resolveConversationId; /** * Whether a message may join the active run as live input instead of running as * its own turn. * * Only when that run is this message's OWN — same physical Slack thread. When * two threads resolve to one conversation they share the harness session and * its single live-input mailbox, so an offer from the other thread would be * applied to a run that is streaming somewhere else, and its sender would get * an acknowledgement reaction instead of an answer. A cron/proactive run holds * a `proactive:` key that matches no thread, so an inbound message can never * steer one. Falling through to the queued path costs a wait and answers in the * right thread. With no run active the responder reports `inactive` anyway, so * this stays out of the way. */ private liveInputAllowedFrom; private respondToEvent; /** * Identity-checked release: a turn that never claimed the conversation, or one * whose claim a later run already took over, must not free someone else's. */ private releaseConversationClaim; private finishCancelledUnlessAcknowledged; /** * Download each inbound Slack file's bytes into an {@link AgentAttachment}. * Files with a missing/disallowed mimetype, no private URL, or an advertised * size over the cap are skipped before any network call. The byte cap is also * enforced during the download. A failed download skips that file and * continues; downloads are tied to the request abort signal. */ private downloadAttachments; private registerController; private unregisterController; private normalizeEventCallback; private prepareText; private prepareCommandText; private isAuthorized; } //# sourceMappingURL=adapter.d.ts.map