import type { UserContent } from "ai"; import type { ChannelResolveSession, ChannelSource } from "#channel/channel-operations.js"; import type { Session } from "#channel/session.js"; import type { SessionAuthContext, TurnPolicy } from "#channel/types.js"; import type { VercelConnectMetadata } from "#shared/vercel-connect-metadata.js"; import type { CardElement } from "#compiled/chat/index.js"; import type { SessionContext } from "#public/definitions/callback-context.js"; import type { ChannelContinuationOps } from "#public/definitions/channel.js"; import type { UnstampedMessageStreamEvent } from "#protocol/message.js"; import type { InputRequest, InputResponse, StrictInputResponses, ValidatedInputResponse } from "#shared/input.js"; import { type SlackBotToken, type SlackHandle, type SlackThread, type SlackWorkspaceHandle } from "#public/channels/slack/api.js"; import { type SlackTransportOptions } from "#public/channels/slack/transport.js"; import { type SlackRenderedEvent, type SlackRenderer } from "#public/channels/slack/renderers.js"; import { type SlackTaskCardState } from "#public/channels/slack/task-card.js"; import type { SlackThreadStatus } from "#public/channels/slack/thread-status.js"; import { type SlackEvent, type SlackEventEnvelope, type SlackMessage } from "#public/channels/slack/inbound.js"; import { type LoadThreadContextMessagesOptions } from "#public/channels/slack/thread.js"; import { type SlackSessionOperations } from "#public/channels/slack/session-operations.js"; import { type UploadPolicyInput } from "#public/channels/upload-policy.js"; import { type SlackWebhookVerifier } from "#public/channels/slack/verify.js"; import { type Channel } from "#public/definitions/channel.js"; import { type ChannelAudience } from "#shared/channel-audience.js"; export type { SlackRespondOptions, SlackSendOptions, SlackSessionOperations, } from "#public/channels/slack/session-operations.js"; type EventData = Extract extends { data: infer D; } ? D : undefined; /** * Base Slack context for inbound webhook handlers. These hooks run before the * runtime hydrates session state, so `state` is absent here. * {@link thread} owns thread-scoped operations (`post`, `postEphemeral`, * `startTyping`, `refresh`, `listParticipants`, `recentMessages`, * `mentionUser`); {@link slack} owns Slack identity (`channelId`, `threadTs`, * `teamId`) plus the raw-API escape hatch (`request`, `uploadFiles`). */ export interface SlackContext { readonly thread: SlackThread; readonly slack: SlackHandle; } /** * {@link SlackContext} plus the persisted per-session * {@link SlackChannelState}. Built by the channel's `context()` hook and * extended by {@link SlackEventContext}. */ export interface SlackChannelContext extends SlackContext { state: SlackChannelState; } /** * Slack context handed to `events[type]` handlers. Extends * {@link SlackChannelContext} (`thread`, `slack`, hydrated `state`) with * continuation routing ({@link ChannelContinuationOps}). Unlike the pre-dispatch * {@link SlackContext}, `state` is hydrated here. */ export interface SlackEventContext extends SlackChannelContext, ChannelContinuationOps { } export type { SlackApiResponse, SlackBotToken, SlackHandle, SlackThread, SlackWorkspaceHandle, } from "#public/channels/slack/api.js"; export type { SlackWebhookVerifier } from "#public/channels/slack/verify.js"; type SlackEventHandler = (data: EventData, channel: SlackEventContext, ctx: SessionContext) => void | Promise; /** * Delivery surface handed to a renderer's `authorization.required` handler. The * connection challenge is a credential: anyone who completes the sign-in * binds their identity to this session's connection. So the only * delivery capabilities here are private ones, an ephemeral reply in the * thread or a direct message. There is deliberately no public `post`, * no raw `slack.request` escape hatch, and no full thread handle. An * override can change the words, not the audience. */ export interface SlackAuthorizationEventContext { /** * Ephemeral message in the current thread, visible only to `userId`. * Same contract as {@link SlackThread.postEphemeral}. */ readonly postEphemeral: SlackThread["postEphemeral"]; /** * Direct message to `userId`'s IM conversation with the bot. Same * contract as {@link SlackThread.postDirectMessage} (requires the * `im:write` scope). */ readonly postDirectMessage: SlackThread["postDirectMessage"]; /** * Hydrated per-session channel state. Resolve the recipient with * `slackUsersByPrincipal[eventData.principalId]`. */ readonly state: SlackChannelState; } type SlackSessionFailedHandler = (data: EventData<"session.failed">, channel: SlackEventContext) => void | Promise; /** * JSON-serializable per-session state, stored verbatim across workflow * step boundaries. Anything written here must round-trip through * `JSON.stringify` / `JSON.parse`. */ export interface SlackPendingApprovalCard { /** Thread post announcing a direct-message approval; updated with the outcome. */ readonly announcementTs?: string; /** Separate tool-input details post; deleted once none of its approvals are pending. */ readonly detailsMessageTs?: string; /** Channel containing the approval card; differs from the session channel for DM delivery. */ readonly messageChannelId?: string; readonly messageBlocks: readonly unknown[]; readonly messageTs: string; } export interface SlackChannelState { /** Audience classification captured before the session is dispatched. */ audience?: ChannelAudience; /** Slack channel id seeded by the inbound mention. */ channelId: string | null; /** Slack thread root ts. */ threadTs: string | null; /** Slack team id, when the inbound event carried one. */ teamId: string | null; /** Slack message ts that triggered the active turn. */ triggeringMessageTs?: string | null; /** Slack workspace whose app installation supplies this session's credentials. */ installationTeamId?: string | null; /** * Slack user id of the actor that triggered the current session/turn. * A later message can change it, so sign-in and approval handlers address * recipients through `slackUsersByPrincipal` instead. */ triggeringUserId?: string | null; /** * Buffered text from a `message.completed` event whose `finishReason` * was `"tool-calls"`. The default `actions.requested` handler uses the * first non-empty line as the next typing indicator, surfacing the * model's pre-tool narration instead of the action label. Cleared at * `turn.started` and after use. */ pendingToolCallMessage?: string | null; /** * The thread status the default renderer last set. A later model step shows * it again, as does a new task card, which Slack clears the status for. */ threadStatus?: SlackThreadStatus | null; /** * Connection name to Slack message ts. Each entry is the public * link-free status post created by the default * `authorization.required` handler; the matching * `authorization.completed` handler edits it in place to surface the * resolution outcome. */ pendingAuthMessageTs?: Record; pendingApprovalCards?: Record; /** * Each recent turn's tool calls and the task card eve last wrote for it, by * turn id. The default `turn.waiting` handler names the working tasks. */ taskCards?: Record | null; /** * Tasks that settled since the turn's last model step: each named agent, or * `null` for another task. The default `step.started` handler shows * `Reviewing results...` for the step that reads them. */ pendingTaskResults?: { readonly names: readonly (string | null)[]; readonly turnId: string; } | null; /** * Principal id to Slack user id, recorded as each message or input response * is delivered. Default handlers use it to address the principal named on * `authorization.required` and approval events. */ slackUsersByPrincipal?: Record; /** * Error id of a final reply that never reached Slack. The next delivered * message tells the model the reply was not seen, then clears it. */ undeliveredReplyErrorId?: string | null; } /** * Per-session metadata attached to tracing spans, projected by the * channel's `metadata(state)` hook. Fields mirror the inbound mention * (channel, team, thread, triggering user) and are `null` until an inbound * event seeds them. Open-ended (`Record`) so deployments * can attach extra span attributes. */ export interface SlackInstrumentationMetadata extends Record { readonly channelId: string | null; readonly teamId: string | null; readonly threadTs: string | null; readonly triggeringUserId: string | null; } /** * Slack channel credentials: outbound bot token plus inbound webhook * verification. Any field may be omitted to fall back to its env-var / * signing-secret default. */ export interface SlackChannelCredentials { /** * Bot token for all outbound Slack Web API calls. Function providers receive * the app installation workspace id when Slack supplies one. Falls back to * `process.env.SLACK_BOT_TOKEN` when omitted. */ readonly botToken?: SlackBotToken; /** * Signing secret used to HMAC-verify inbound webhook requests. Falls * back to `process.env.SLACK_SIGNING_SECRET` when neither this nor * `webhookVerifier` is supplied. */ readonly signingSecret?: string; /** * Custom inbound webhook verifier. When supplied, eve skips the * `SLACK_SIGNING_SECRET` fallback and delegates to it. Typically set by * integrations (e.g. Connect) that authenticate webhooks out-of-band. */ readonly webhookVerifier?: SlackWebhookVerifier; /** Build-time metadata supplied by Vercel Connect credential helpers. */ readonly vercelConnect?: VercelConnectMetadata; } /** Target accepted by `ctx.to(slack, target)` from route and schedule handlers. */ export interface SlackReceiveTarget { readonly channelId: string; readonly threadTs?: string; /** * Optional audience for proactive receives. Omit to leave `unknown`. * Pass when the caller already knows channel visibility, for example a * webhook or schedule that classified the Slack destination before handoff. */ readonly audience?: ChannelAudience; /** Slack workspace whose app installation supplies credentials for this send. */ readonly installationTeamId?: string; /** * Optional message posted into the Slack channel before the agent runs. * The post becomes the thread root and the first turn is threaded under * it, giving cross-channel handoffs a visible context anchor. Mutually * exclusive with {@link threadTs}. */ readonly initialMessage?: SlackInitialMessage; } /** * Pre-agent post issued by `slackChannel().receive` when the caller * provides `target.initialMessage`. Mirrors `ctx.thread.post`'s card * variant so the same `Card({...})` construction can be reused. */ export interface SlackInitialMessage { readonly card: CardElement; readonly fallbackText?: string; } /** * Options for one turn requested by a generic Slack event handler. */ export interface SlackEventSendOptions { readonly auth: SessionAuthContext | null; readonly target: SlackReceiveTarget; /** Overrides the workflow run title without changing the message sent to the model. */ readonly title?: string; } /** Options for answering pending input requests from a generic Slack event handler. */ export interface SlackEventRespondOptions { readonly auth: SessionAuthContext | null; readonly target: SlackSessionTarget; } /** * Starts a session on the current Slack channel from `onEvent`. Call it zero, * one, or many times; each invocation returns the resulting session. */ export type SlackEventSendFn = (message: string | UserContent, options: SlackEventSendOptions) => Promise; /** Answers pending input requests on one Slack thread. */ export type SlackEventRespondFn = (inputResponses: StrictInputResponses, options: SlackEventRespondOptions) => ReturnType; /** * Slack thread identity used by workspace-scoped inbound helpers. */ export interface SlackSessionTarget { readonly channelId: string; readonly threadTs: string; } /** * Imperative surface handed to `slackChannel({ onEvent })`. Generic Events API * payloads are not necessarily tied to one thread, so the context exposes a * workspace API handle plus Slack-bound operations rather than the * thread-scoped {@link SlackContext} used by message handlers. */ export interface SlackInboundEventContext { /** Starts a turn on this Slack channel using the proactive send contract. */ readonly send: SlackEventSendFn; /** Answers pending input requests on one Slack thread. */ readonly respond: SlackEventRespondFn; /** Cancels the active turn for one Slack thread. */ cancel(input: { readonly target: SlackSessionTarget; readonly turnId?: string; }): ReturnType["cancel"]>; /** Compacts the current session for one Slack thread. */ compact(input: { readonly target: SlackSessionTarget; }): ReturnType["compact"]>; /** Clears the current session for one Slack thread. */ clear(input: { readonly target: SlackSessionTarget; }): ReturnType["clear"]>; /** Resets the current session for one Slack thread. */ reset(input: { readonly reason?: string; readonly target: SlackSessionTarget; }): ReturnType["reset"]>; /** Resolves the current session for one Slack thread. */ resolveSession(input: { readonly target: SlackSessionTarget; }): ReturnType; /** The complete signed Events API callback envelope. */ readonly envelope: SlackEventEnvelope; /** Workspace-scoped Slack identity and raw Web API escape hatch. */ readonly slack: SlackWorkspaceHandle; /** Keeps detached handler work alive after the Slack webhook is acknowledged. */ readonly waitUntil: (task: Promise) => void; } /** * Message-scoped context handed to `onMessage`, `onAppMention`, and * `onDirectMessage`. */ export interface SlackInboundMessageContext extends SlackContext, SlackSessionOperations { /** * Returns whether the inbound message belongs to a DM, group DM, or private channel. * Unknown conversation types fail closed and return `true`. */ isDMOrPrivateChannel(): Promise; /** Returns whether this message belongs to a thread with an active eve session. */ isSubscribed(): Promise; /** Returns whether the inbound event explicitly mentions this bot. */ isBotMentioned(): boolean; } /** Interaction-scoped context handed to `slackChannel({ onInteraction })`. */ export interface SlackInteractionContext extends SlackContext, SlackSessionOperations { } /** Workspace-scoped context handed to `slackChannel({ onShortcut })`. */ export interface SlackShortcutContext { /** Slack workspace identity and raw Web API escape hatch. */ readonly slack: SlackWorkspaceHandle; } /** Workspace-scoped context handed to `slackChannel({ onSlashCommand })`. */ export interface SlackSlashCommandContext { /** Slack workspace identity and raw Web API escape hatch. */ readonly slack: SlackWorkspaceHandle; } /** Context handed to `slackChannel({ onInputResponse })` before eve resumes HITL. */ export interface SlackInputResponseContext extends SlackContext { /** Auth derived from the Slack user who submitted the signed interaction. */ readonly defaultAuth: SessionAuthContext; } export interface SlackInteractionAction { readonly actionId: string; readonly value?: string; readonly blockId?: string; /** * `selected_option.value` for radio / select / external_select * widgets. `undefined` for buttons and multi-select widgets. */ readonly selectedOptionValue?: string; /** * `ts` of the Slack message hosting the clicked component. Required to * update that message in place via `chat.update`, since `ctx.slack.threadTs` * resolves to the thread root (not the clicked message) for components * inside thread replies. */ readonly messageTs?: string; /** * Slack `trigger_id` of the click. Pass it to `views.open` (for example * through `ctx.slack.request`) to open a modal from a message button. Slack * accepts it for about 3 seconds, so call `views.open` first in the handler. */ readonly triggerId?: string; /** * Display label of the clicked widget: `text.text` for buttons, * `selected_option.text.text` for radio/static_select. Renders the * "answered" card without re-fetching the original request. */ readonly label?: string; /** * Slack actor who triggered the interaction, letting `onInteraction` * handlers attribute resolutions back to the clicker without re-parsing * the raw payload. Always present, since Slack requires `user` on every * `block_actions` payload. */ readonly user: SlackInteractionUser; } /** Slack actor on an interactive payload, mirroring `body.user`. */ export interface SlackInteractionUser { readonly id: string; /** Modern canonical display handle. */ readonly username?: string; /** Legacy display handle, kept for older workspaces. */ readonly name?: string; } /** Decoded Slack slash command invocation. */ export interface SlackSlashCommand { /** Configured command name, including its leading slash. */ readonly command: string; readonly text: string; readonly user: SlackInteractionUser; readonly teamId?: string; readonly channelId: string; readonly channelName?: string; readonly enterpriseId?: string; readonly isEnterpriseInstall: boolean; readonly triggerId?: string; readonly responseUrl?: string; } /** Message selected through a Slack message shortcut. */ export interface SlackShortcutMessage { readonly text: string; readonly ts: string; readonly threadTs?: string; readonly userId?: string; } /** Decoded Slack message shortcut or global shortcut. */ export type SlackShortcut = { readonly type: "message_action"; readonly callbackId: string; readonly triggerId: string; readonly user: SlackInteractionUser; readonly teamId?: string; readonly channelId: string; readonly message: SlackShortcutMessage; readonly responseUrl?: string; } | { readonly type: "shortcut"; readonly callbackId: string; readonly triggerId: string; readonly user: SlackInteractionUser; readonly teamId?: string; }; /** Decoded eve-owned HITL response submitted through Slack interactivity. */ export type SlackInputResponseSubmission = { readonly type: "block_actions"; readonly inputResponses: readonly ValidatedInputResponse[]; readonly actions: readonly SlackInteractionAction[]; readonly messageTs?: string; readonly user: SlackInteractionUser; } | { readonly type: "view_submission"; readonly inputResponses: readonly ValidatedInputResponse[]; readonly messageTs: string; readonly user: SlackInteractionUser; }; /** Result of a Slack HITL response admission hook. Return `null` to reject. */ export type SlackInputResponseResult = { readonly auth: SessionAuthContext | null; } | null; /** * Result of an `onAppMention` or `onDirectMessage` callback. Return an * object (auth may be `null`) to dispatch a turn, or `null` to drop the * inbound message. `context` strings are appended as user messages to * session history before the delivery message. `title` overrides the * workflow run title without changing the message sent to the model. */ export type SlackMentionResult = { readonly auth: SessionAuthContext | null; readonly context?: readonly string[]; readonly title?: string; } | null; export type SlackMentionResultOrPromise = SlackMentionResult | Promise; /** * Alias of {@link SlackMentionResult} for the `onDirectMessage` signature, * so DM handlers do not read in terms of "mention". */ export type SlackInboundResult = SlackMentionResult; /** {@link SlackInboundResult}, or a promise resolving to one. */ export type SlackInboundResultOrPromise = SlackMentionResultOrPromise; /** * eve's full-context Slack event handlers: the innermost link of every * channel's renderer chain. The default `authorization.required` handler keeps * the full {@link SlackEventContext} because it owns the public link-free * status, which authored renderers, limited to private delivery, can't post. */ export type SlackChannelInternalEvents = { readonly [T in Exclude]?: SlackEventHandler; } & { readonly "session.failed"?: SlackSessionFailedHandler; }; export type SlackApprovalChannel = "direct-message" | "thread"; /** Input request passed to a {@link SlackApprovalChannelResolver}. */ export type SlackApprovalRequest = InputRequest; /** Chooses the Slack destination for one input request. */ export type SlackApprovalChannelResolver = (request: SlackApprovalRequest, ctx: SessionContext) => SlackApprovalChannel | Promise; export interface SlackChannelConfig { /** Slack API transport overrides: base URLs and a custom fetch. */ readonly api?: SlackTransportOptions; readonly credentials?: SlackChannelCredentials; readonly botName?: string; /** * Chooses where each input request is delivered. Direct-message requests go to the * Slack user who triggered the active turn; the session thread names that reviewer * without exposing the request. Defaults to `"thread"` when omitted. */ readonly approvalChannel?: SlackApprovalChannelResolver; /** Override the default webhook route path (`/eve/v1/slack`). */ readonly route?: string; /** Policy for accepted messages that arrive while a turn is active. */ readonly turnPolicy?: TurnPolicy; /** * Inbound upload policy applied to file attachments before they reach * the harness. Violating attachments are dropped with a warning so the * mention's text portion still gets delivered. Pass `"disabled"` to * reject every attachment. Defaults to the framework's 25 MB cap with * unrestricted media types. */ readonly uploadPolicy?: UploadPolicyInput; /** * Adds earlier replies from the current Slack thread to each triggering * turn. Messages are rendered with their Slack sender ids attached so a * multi-user transcript retains unambiguous speaker attribution. Omit this * option to avoid fetching thread history. */ readonly threadContext?: LoadThreadContextMessagesOptions; /** * Handles human-authored Slack messages. Specialized `onAppMention` and * `onDirectMessage` handlers take precedence for their event types. Other * channel messages are ignored when this hook is omitted. */ onMessage?(ctx: SlackInboundMessageContext, message: SlackMessage): SlackInboundResultOrPromise; /** * Invoked when a Slack `app_mention` event arrives (only `app_mention`; * other event types are ignored). Decides whether to dispatch and with * what auth. It renders nothing: the renderer chain's `received` * acknowledges the mention while this hook runs. * * Return `{ auth }` to dispatch with that session auth context, or `null` * to drop the mention. May be sync or async; the result is awaited before * dispatching. Thrown errors are caught and logged and the mention is * dropped. Defaults to a workspace-scoped auth derivation. */ onAppMention?(ctx: SlackInboundMessageContext, message: SlackMessage): SlackMentionResultOrPromise; /** * Invoked on a direct message: a Slack `message` event with * `channel_type: "im"`. Subtype messages (edits, deletes, joins, etc.) * and bot messages (`bot_id` set, including the bot's own replies) are * filtered out first, so handlers only see plain user-authored DMs. * Decides whether to dispatch and with what auth. It renders nothing: the * renderer chain's `received` acknowledges the message while this hook runs. * * Return `{ auth }` to dispatch with that session auth context, or `null` * to drop the message. May be sync or async; the result is awaited before * dispatching. Thrown errors are caught and logged and the message is * dropped. Defaults to a workspace-scoped auth derivation. * Requires the bot's Slack app to subscribe to `message.im` with the * `im:history` scope. */ onDirectMessage?(ctx: SlackInboundMessageContext, message: SlackMessage): SlackInboundResultOrPromise; /** * Fallback handler for signed Slack Events API callbacks. An authored * `onAppMention` or `onDirectMessage` takes precedence for events accepted * by that specialized handler; otherwise the raw event arrives here. When * neither a specialized handler nor `onEvent` is authored, mentions and DMs * retain their built-in defaults and other event types are ignored. * * The handler owns control flow. Call `ctx.send(...)` zero, one, or many * times to start turns on Slack, and use `ctx.waitUntil(...)` for detached * work. The return value is ignored. Runs after the webhook has been * acknowledged through the host's `waitUntil` mechanism. Errors are caught * and logged and never fall through to another handler. * * URL verification, slash commands, and interactive payloads are not Events * API callbacks and do not reach this handler. */ onEvent?(ctx: SlackInboundEventContext, event: SlackEvent): void | Promise; /** * Handler for Slack `block_actions` interactive callbacks (button * clicks, select changes, etc.) **not** consumed by the framework's * HITL pipeline. Slack POSTs interactive payloads to the same webhook * route as mentions; the framework decodes them, routes any action whose * `action_id` starts with `eve_input:` to the runtime as an HITL * response (resuming a paused session), and forwards everything else * here, one invocation per non-HITL action. * * Runs on the inbound webhook side via `waitUntil()`, so the channel * returns `200 OK` immediately. Errors are caught and logged; they do * not affect the webhook response or sibling invocations. * * The `SlackContext` here is rebuilt from the interaction payload * (channel id, thread ts, team id), **not** the persisted thread state * used by event handlers. Use `ctx.slack.request(...)` for arbitrary * Slack Web API calls and `action.messageTs` to target `chat.update`. */ onInteraction?(action: SlackInteractionAction, ctx: SlackInteractionContext): void | Promise; /** * Handles Slack message shortcuts (`message_action`) and global shortcuts * (`shortcut`) with workspace-scoped Slack Web API access. * * eve returns `200 OK` immediately and keeps the handler alive through * `waitUntil()`. Errors are caught and logged. */ onShortcut?(shortcut: SlackShortcut, ctx: SlackShortcutContext): void | Promise; /** * Handles Slack slash commands configured with this channel's webhook route. * The command includes the configured command name, arguments, invoking user, * channel, workspace, trigger ID, and response URL. * * eve returns an empty `200 OK` immediately and keeps the handler alive * through `waitUntil()`. Errors are caught and logged. */ onSlashCommand?(command: SlackSlashCommand, ctx: SlackSlashCommandContext): void | Promise; /** * Authorizes an eve-owned HITL answer before the pending input resolves. * Return `{ auth }` to accept the response, or `null` to reject it and keep * the request pending. Thrown errors are logged and treated as rejection. * * When this hook is omitted, Slack preserves its built-in behavior and * accepts the response with the submitting user's auth, regardless of other * authored handlers. */ onInputResponse?(ctx: SlackInputResponseContext, submission: SlackInputResponseSubmission): SlackInputResponseResult | Promise; /** * Renderers that wrap eve's default Slack rendering, outermost first. Omit * it and eve renders with its default alone. Each renderer can acknowledge * messages (`received`), render session events (`events`), and shape the * task card (`taskCard`). A handler runs the rest of the chain, ending with * eve's default, by calling `next`, and skips it by not calling it. */ readonly renderers?: readonly SlackRenderer[]; } /** * Concrete return type of {@link slackChannel}. Named so consumers can * default-export a `slackChannel(...)` call under `declaration: true` * without TypeScript emitting an internal path for `Channel`. */ export interface SlackChannel extends Channel { } /** * Slack channel factory. Wires up the webhook route, message dispatch, * interaction handling, and eve's default rendering: the `Thinking...` * acknowledgement, status lines, replies, questions, sign-ins, errors, and a * live task card for each turn that starts tasks. Message hooks decide whether * and how to dispatch; `renderers` wrap the default rendering without * replacing it. */ export declare function slackChannel(config?: SlackChannelConfig): SlackChannel;