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 { MemorySettingsStorage } from '../../storage/domains/memory-settings/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 an ephemeral "pick a default * factory" card 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; /** * Observational-memory settings domain. When provided, a repo-backed session * adopts its factory project's shared memory settings on start, matching the * web kickoff. */ memorySettings?: MemorySettingsStorage; /** * Factory work-items domain. When provided, a dispatched new-session thread * (DM or mention) upserts a Work-board card in Building (`execute`) carrying * the Slack thread as its external source and binding the repo-backed * session. Best-effort — a failure never blocks the run. Unset → no card. */ workItems?: WorkItemsStorage; /** * Work-item feed. With it (and `workItems`), an `aside` lands as a comment on * the card the thread created. 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; slackWorkItemsEnabled: boolean; }; /** * 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 — an ephemeral "pick a default factory" * card deep-linking to settings, and the run is blocked. */ export declare function resolveFactoryForLink({ thread, message, link, key, accountLinks, projects, }: { thread: HandlerThread; message: HandlerMessage; 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. Everything else * (unlinked, unrouted, repo-less, or no source control) keeps the chat-only * `defaultResourceId`. * * 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. * * Skips a session whose mode 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 the factory default 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; /** * Upsert the Work-board card for a dispatched Slack-thread run. Keyed on the * thread via `externalSource` — the work-items domain's unique * `(factory_project_id, source_key)` index makes repeat messages reuse the * same card, and `reuseMode: 'preserve'` keeps a card a human already dragged * across stages untouched. The card lands in Building (`execute`) for every * dispatched thread (DM or mention) — there is deliberately no per-origin * stage split; smart routing is a follow-up. * * The session id / branch / threadId and the workspace deep-link are resolved * by the caller (which already looked up the internal thread), so this helper * just shapes and writes. Best-effort: the run is already dispatched, so a * failure logs instead of throwing — work-item creation must never abort a Slack run. */ export declare function upsertThreadWorkItem({ workItems, thread, message, link, factoryProjectId, session, url, }: { workItems: WorkItemsStorage; thread: HandlerThread; message: HandlerMessage; link: ChannelAccountLink; factoryProjectId: string; /** * The repo-backed Factory session to bind under the `chat` role, or * `undefined` for a chat-only thread (no Factory session to bind). */ session?: { sessionId: string; branch: string; threadId: string; }; /** Workspace deep-link to the running session; omitted when no public URL. */ url?: string; }): Promise; 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