import type { AgentMessageFinishOptions, AgentMessageStream as AgentMessageStreamBase, AgentStreamEvent, ChannelDeliveryDisposition, ChannelMessageContentKind, ResilientMessageStreamLogger } from "@mono-agent/agent-contracts"; import type { SlackChannelId, SlackMessageTs, SlackWebApi } from "./types.js"; export interface AgentMessageStream extends AgentMessageStreamBase { status(text: string): Promise; append(delta: string): Promise; replace(text: string): Promise; event(event: AgentStreamEvent): Promise; finish(finalText?: string, options?: AgentMessageFinishOptions): Promise; } export type SlackMessageStreamLogger = ResilientMessageStreamLogger; export interface SlackMessageStreamOptions { api: SlackWebApi; channelId: SlackChannelId; threadTs?: SlackMessageTs; /** Stable UUID forwarded to chat.postMessage for duplicate suppression. */ clientMsgId?: string; /** * Compatibility request for notification-suppressed delivery. Slack's * `chat.postMessage` API has no bot-controlled suppression field, so this * stream posts normally and, when a logger is configured, emits one explicit * warning before its first post. */ silent?: boolean; /** Message ts to react to (๐Ÿ‘€ "seen") in final-only mode. */ reactToTs?: SlackMessageTs; /** * Deliver only the final answer: suppress interim edits and react ๐Ÿ‘€ ("seen") * while the agent works. Default false. */ finalOnly?: boolean; initialStatusText?: string; editDebounceMs?: number; maxMessageChars?: number; /** Maximum retries for a *final* delivery before giving up. Default 3. */ maxSendRetries?: number; /** Upper bound on any honored `retry-after`/backoff wait, in ms. Default 60000. */ retryCapMs?: number; /** Base delay for exponential backoff between final-delivery retries. Default 500. */ retryBaseDelayMs?: number; /** Render lightweight tool activity hints as the live status. Default true. */ showHints?: boolean; /** * Status shown via `assistant.threads.setStatus` ("App is ") while the * agent works, when this is a Slack AI-assistant thread. Falls back to the ๐Ÿ‘€ * reaction in regular channels/DMs. Default "is thinkingโ€ฆ". */ assistantStatusText?: string; /** Aborts in-flight retry waits (e.g. on /cancel). */ abortSignal?: AbortSignal; logger?: SlackMessageStreamLogger; /** * Notified with each message this stream posts. Used to link a posted message * back to the conversation that produced it, so a later in-thread reply can * resume that conversation (see `posted-message-index` in agent-app). */ onPosted?: SlackPostedMessageListener; /** Notified after a status or answer post/edit has a native Slack receipt. */ onDeliveryReceipt?: SlackDeliveryReceiptListener; } /** Notified with the channel + ts of a message a stream posted. */ export type SlackPostedMessageListener = (ref: { ts: SlackMessageTs; channel: SlackChannelId; }) => void; export interface SlackDeliveryReceipt { readonly ts: SlackMessageTs; readonly channel: SlackChannelId; readonly contentKind: ChannelMessageContentKind; readonly operation: "post" | "edit"; } export type SlackDeliveryReceiptListener = (receipt: SlackDeliveryReceipt) => void; /** * Raised only when a *final* delivery cannot reach Slack after retries and the * last-resort fresh post. The AI request itself already succeeded, so the * adapter treats this as a degraded delivery โ€” never as an agent failure. * * Retained as the adapter's public error type; the shared substrate raises a * {@link ChannelDeliveryError}, which this class normalizes so callers continue * to catch `SlackDeliveryError`. */ export declare class SlackDeliveryError extends Error { readonly cause: unknown; readonly attempts: number; readonly disposition: ChannelDeliveryDisposition; constructor(message: string, details: { cause: unknown; attempts: number; disposition?: ChannelDeliveryDisposition; }); } /** How a failed Slack post/update should be handled. */ export type SlackSendOutcome = { kind: "recreate"; failureCertainty: "not_delivered" | "unknown"; } | { kind: "reformat_plain"; failureCertainty: "not_delivered" | "unknown"; } | { kind: "retry"; retryAfterMs?: number; failureCertainty: "not_delivered" | "unknown"; } | { kind: "fatal"; failureCertainty: "not_delivered" | "unknown"; }; /** * Slack's hard TRUNCATION ceiling for `chat.postMessage` text โ€” the point past * which Slack discards characters. It is NOT a usable message size and is not * the transport default: Slack documents 4,000 characters as the practical limit * and, well below the ceiling, silently breaks a long post into several messages * of its own choosing, returning only the LAST fragment's `ts`. That loses the * adapter's control of where the text breaks and misanchors the posted-message * index on the orphaned tail. * * So this survives only as the upper bound for an operator-supplied override. * The default is {@link DEFAULT_MAX_MESSAGE_CHARS}, comfortably under the zone * where Slack starts splitting. Slack documents no exact split threshold ("may * be broken into multiple messages"), so the margin is deliberate rather than * tuned to an observed boundary. * * @see https://docs.slack.dev/reference/methods/chat.postMessage * @see https://docs.slack.dev/changelog/2018-truncating-really-long-messages */ export declare const SLACK_MAX_MESSAGE_CHARS = 40000; /** * Thin wrapper over the shared {@link ResilientMessageStream}. It builds a * {@link SlackChannelTransport} and delegates the streaming/resilience FSM, * preserving the adapter's public surface: the `status/append/replace/event/ * finish` API, friendly tool hints, abort-aware retries, and a Slack-shaped * delivery error. */ export declare class SlackMessageStream implements AgentMessageStream { private readonly transport; private readonly inner; private readonly finalOnly; constructor(options: SlackMessageStreamOptions); status(text: string): Promise; append(delta: string): Promise; replace(text: string): Promise; event(event: AgentStreamEvent): Promise; dismissTransient(): Promise; finish(finalText?: string, options?: AgentMessageFinishOptions): Promise; } /** * Classify a Slack post/update failure into a recovery strategy. Pure and * exported so the recovery policy can be unit-tested directly. */ export declare function classifySlackError(error: unknown): SlackSendOutcome; //# sourceMappingURL=message-stream.d.ts.map