From a65f457f52e2cf8ed5b202c68ebe455f5859b7a6 Mon Sep 17 00:00:00 2001 From: Jeffrey Marcilliat Date: Sun, 25 Jan 2026 21:58:32 +0000 Subject: [PATCH] feat(slack): add configurable loading messages and typing status Add support for customizing the Slack thread status messages shown while processing requests: - loadingMessages: string[] - Array of messages that rotate randomly during processing (passed to Slack's assistant.threads.setStatus API) - typingStatus: string - Custom typing indicator text (default: 'is typing...') These can be configured per Slack account in channels.slack config: ```yaml channels: slack: typingStatus: 'is thinking...' loadingMessages: - 'Pondering...' - 'Processing...' - 'Almost there...' ``` --- src/config/types.slack.ts | 4 ++++ src/slack/monitor/context.ts | 13 ++++++++++++- src/slack/monitor/message-handler/dispatch.ts | 3 ++- src/slack/monitor/provider.ts | 4 ++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/config/types.slack.ts b/src/config/types.slack.ts index 564248503..d2fdc4558 100644 --- a/src/config/types.slack.ts +++ b/src/config/types.slack.ts @@ -77,6 +77,10 @@ export type SlackThreadConfig = { export type SlackAccountConfig = { /** Optional display name for this account (used in CLI/UI lists). */ name?: string; + /** Custom loading messages shown while processing (rotates randomly). */ + loadingMessages?: string[]; + /** Status text shown while typing (default: "is typing..."). */ + typingStatus?: string; /** Slack connection mode (socket|http). Default: socket. */ mode?: "socket" | "http"; /** Slack signing secret (required for HTTP mode). */ diff --git a/src/slack/monitor/context.ts b/src/slack/monitor/context.ts index 83acfbae6..cf8e7970f 100644 --- a/src/slack/monitor/context.ts +++ b/src/slack/monitor/context.ts @@ -111,7 +111,10 @@ export type SlackMonitorContext = { channelId: string; threadTs?: string; status: string; + loadingMessages?: string[]; }) => Promise; + loadingMessages?: string[]; + typingStatus: string; }; export function createSlackMonitorContext(params: { @@ -148,6 +151,8 @@ export function createSlackMonitorContext(params: { ackReactionScope: string; mediaMaxBytes: number; removeAckAfterReply: boolean; + loadingMessages?: string[]; + typingStatus?: string; }): SlackMonitorContext { const channelHistories = new Map(); const logger = getChildLogger({ module: "slack-auto-reply" }); @@ -247,14 +252,18 @@ export function createSlackMonitorContext(params: { channelId: string; threadTs?: string; status: string; + loadingMessages?: string[]; }) => { if (!p.threadTs) return; - const payload = { + const payload: Record = { token: params.botToken, channel_id: p.channelId, thread_ts: p.threadTs, status: p.status, }; + if (p.loadingMessages && p.loadingMessages.length > 0) { + payload.loading_messages = p.loadingMessages; + } const client = params.app.client as unknown as { assistant?: { threads?: { @@ -400,5 +409,7 @@ export function createSlackMonitorContext(params: { resolveChannelName, resolveUserName, setSlackThreadStatus, + loadingMessages: params.loadingMessages, + typingStatus: params.typingStatus ?? "is typing...", }; } diff --git a/src/slack/monitor/message-handler/dispatch.ts b/src/slack/monitor/message-handler/dispatch.ts index d31885cfa..563f72d77 100644 --- a/src/slack/monitor/message-handler/dispatch.ts +++ b/src/slack/monitor/message-handler/dispatch.ts @@ -63,7 +63,8 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag await ctx.setSlackThreadStatus({ channelId: message.channel, threadTs: statusThreadTs, - status: "is typing...", + status: ctx.typingStatus, + loadingMessages: ctx.loadingMessages, }); }, stop: async () => { diff --git a/src/slack/monitor/provider.ts b/src/slack/monitor/provider.ts index 366a32a34..2a05634fd 100644 --- a/src/slack/monitor/provider.ts +++ b/src/slack/monitor/provider.ts @@ -123,6 +123,8 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions"; const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024; const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false; + const loadingMessages = slackCfg.loadingMessages; + const typingStatus = slackCfg.typingStatus; const receiver = slackMode === "http" @@ -204,6 +206,8 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { ackReactionScope, mediaMaxBytes, removeAckAfterReply, + loadingMessages, + typingStatus, }); const handleSlackMessage = createSlackMessageHandler({ ctx, account });