import type { ChannelHandler, ChannelHandlers, ChannelSessionResolve, ChannelSessionStart, ResolveResourceId, ResolveThreadId } from '@mastra/core/channels'; import type { SlackAdapterChannelConfig } from '@mastra/slack'; import type { ChannelAccountLink, ChannelAccountLinkKey, ChannelIdentityStorage } from '../../storage/domains/channel-identity/base.js'; import type { CommentsDomain } from '../../storage/domains/comments/domain.js'; import type { ModelDefaultsStorage } from '../../storage/domains/model-defaults/base.js'; import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js'; import type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js'; import type { ExternalWorkItemSource, WorkItemsStorage } from '../../storage/domains/work-items/base.js'; import type { FactoryChannelsConfig } from '../base.js'; type HandlerThread = Parameters[0]; type HandlerMessage = Parameters[1]; /** Dependencies the Slack channel handlers close over, injected from the web entry. */ interface SlackChannelDeps { /** * The factory's reverse-index store mapping a Slack sender to a Mastra * tenant. When provided, inbound messages from an unlinked sender are not * dispatched — the run only proceeds (with the sender's tenant stamped on * the request context) once they've linked their account. Unlinked senders * get an ephemeral "connect your account" card instead. */ accountLinks?: ChannelIdentityStorage; /** * Factory projects domain. When provided (alongside `accountLinks`), a * linked sender's run must also resolve to a Factory project before it * dispatches: their link's default factory, else their tenant's only * factory (stamped back onto the link), else a "pick a default factory" * card posted in the thread and no run. Unset → no factory routing (runs * dispatch as before). */ projects?: FactoryProjectsStorage; /** * Storage handle of the integration that owns source control * (`IntegrationContext.storage.sourceControlOwner`). Used to make new Slack * threads repo-backed: when the sender is linked and their factory has a * repository, the thread's resourceId becomes a Factory user-session id (repo * cloned on a `slack/{threadTs}` branch) instead of the chat-only * `channel:...` id. It also lets a started session read back the project it * belongs to. Nothing here is provider-specific — the connection is matched * by the handle's own `integrationId`. Absent (no source-control integration * registered) → chat-only sessions as before. */ sourceControl?: SourceControlStorageHandle; /** Registered source-control partitions used to select the provider linked to each Factory project. */ sourceControls?: readonly SourceControlStorageHandle[]; /** * Model-defaults domain. When provided, a new repo-backed session starts on * the linked sender's default model before the factory's shared default. Read * once, when the thread's build model is first chosen; the choice is persisted * on the thread and never re-resolved. Unset → the factory default, as before. */ modelDefaults?: ModelDefaultsStorage; /** * Factory work-items domain, used to find the card a Slack thread already * has so an `aside` can land on it. New threads never create cards. */ workItems?: WorkItemsStorage; /** * Work-item feed. With it (and `workItems`), an `aside` lands as a comment on * the card the thread already has. Unset → asides stay ignored, as before. */ feed?: Pick; /** Overrides applied to the Slack channel adapter entry. */ adapterOptions?: SlackAdapterChannelConfig; } /** Outcome of the sender-link gate for one inbound message. */ type LinkedSenderResult = /** Gating not configured — dispatch as before account linking existed. */ { status: 'ungated'; } /** Sender unlinked — Connect card posted (when possible), do not dispatch. */ | { status: 'blocked'; } /** Sender linked — their tenant plus the sender key the link lives under. */ | { status: 'linked'; link: ChannelAccountLink; key: ChannelAccountLinkKey; }; /** * Resolve the sender's account link, posting an ephemeral "connect your * account" card (visible only to the sender) linking into the web UI's * Slack-connect flow when they're unlinked. */ export declare function resolveLinkedSender({ thread, message, accountLinks, }: { thread: HandlerThread; message: HandlerMessage; accountLinks?: ChannelIdentityStorage; }): Promise; /** Outcome of factory routing for one linked sender's inbound message. */ type FactoryRouteResult = /** Factory routing not configured — dispatch without a factory. */ { status: 'ungated'; } /** No factory resolved — prompt card posted (when possible), do not dispatch. */ | { status: 'blocked'; } /** The Factory project this sender's runs route to. */ | { status: 'resolved'; factoryProjectId: string; }; /** * Decide which Factory project a linked sender's run belongs to: * * 1. The link's `defaultFactoryProjectId`, when it still exists (a stale id — * deleted factory — falls through as if unset). * 2. Else, the tenant's only factory, stamped back onto the link so it shows * up (and stays editable) in Connected Accounts settings. * 3. Else — zero or several factories — a "pick a default factory" card * posted publicly in the thread so Slack notifies the sender, and the run is * blocked. */ export declare function resolveFactoryForLink({ thread, link, key, accountLinks, projects, }: { thread: HandlerThread; link: ChannelAccountLink; key: ChannelAccountLinkKey; accountLinks: ChannelIdentityStorage; projects?: FactoryProjectsStorage; }): Promise; /** * Resolve the resourceId for a NEW Slack channel thread. A linked sender whose * factory has a repository gets a Factory user-session id — the controller * session then materializes the repo sandbox via the factory's dynamic * workspace (clone + PAT), the session shows up in the web Sessions list, and * View Session deep-links land on the normal workspace route. * * A gated deployment refuses a thread it cannot place, rather than falling back * to a chat-only session: a repo-backed thread is the whole point of the * integration, and answering in a chat-only one would run the sender's request * in no project, on the SDK's built-in defaults. A linked sender whose project * cannot start a session gets an actionable error in Slack; the dispatch gate * handles unlinked or unrouted senders before this hook runs. * The hook only runs when a NEW thread is actually created (`getOrCreateThread` * resolves the owner lazily), so an established conversation is never touched: * it keeps its session, its model, and its history. * * The chat-only `channel:...` id survives for two cases where refusing would be * wrong: a deployment that cannot produce a repo-backed thread at all (no * account linking, no projects, or no source-control integration registered), * and a message carrying no sender id. In both, a `channel:` thread is the only * shape a session can take. * * Pure lookups only — cards for unlinked/unrouted senders are the dispatch * gate's job; this hook must never post. */ export declare function createChannelResourceIdResolver(deps: SlackChannelDeps): ResolveResourceId; /** * Thread id for a NEW Slack channel thread. Repo-backed threads take the * user-session id AS their thread id, matching the web convention * (FactoryStartCoordinator seeds threads with threadId = sessionId) so * `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created * sessions exactly like web-created ones — no `?resourceId=` override needed. * Chat-only threads keep the default random id: their `channel:...` * resourceId is a memory key, not a unique thread id. */ export declare const resolveChannelThreadId: ResolveThreadId; /** Create channel sessions with their Factory ownership present in initial controller state. */ export declare function createChannelSessionResolver(deps: SlackChannelDeps): ChannelSessionResolve; /** * Apply the factory's configuration to a Slack-created session the first time * its thread reaches the controller. * * Without this a Slack session runs on the SDK's built-in mode default * (`openai/gpt-5.5`), so a factory configured for any other provider fails every * message with a missing-credentials error. The web kickoff has always applied * the factory default; this brings Slack to the same footing. * * Only repo-backed threads are configured. Their resourceId IS the Factory * session id, which the source-control rows turn back into a project — a * chat-only `channel:...` id names no project, so there is nothing to read. * * The model this session starts on is picked here, once, from the project's * effective model scope for a session: the Factory default or the durable session owner's * default, else the SDK's built-in model. The choice is persisted on the thread * as `currentModelId`, so it outlives the process that made it. * * The model resolution is skipped on a session that already has a model * persisted on the thread. That is the durable record of a deliberate choice — * either an earlier start or a user's own switch — and re-applying a preference * over it would undo the user's selection every time the process restarts. */ export declare function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart; /** * A channel id and a `ts` name a thread only inside the workspace that issued * them, so the team scopes the card's key — without it two workspaces could * hold one key and an aside would land on another tenant's card. */ export declare function slackThreadSource(thread: HandlerThread, teamId?: string): ExternalWorkItemSource; export declare const createHandlers: (deps: SlackChannelDeps) => ChannelHandlers; /** Slack app credentials, passed in explicitly rather than read from env here. */ interface SlackCredentials { clientId?: string; clientSecret?: string; signingSecret: string; botToken?: string; } export declare function createSlackChannelsConfig(deps: SlackChannelDeps & { slack: SlackCredentials; }): FactoryChannelsConfig; export {}; //# sourceMappingURL=slack.d.ts.map