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...'
```
This commit is contained in:
Jeffrey Marcilliat 2026-01-25 21:58:32 +00:00
parent 50b4126c79
commit a65f457f52
4 changed files with 22 additions and 2 deletions

View File

@ -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). */

View File

@ -111,7 +111,10 @@ export type SlackMonitorContext = {
channelId: string;
threadTs?: string;
status: string;
loadingMessages?: string[];
}) => Promise<void>;
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<string, HistoryEntry[]>();
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<string, unknown> = {
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...",
};
}

View File

@ -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 () => {

View File

@ -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 });