diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index fd44cca22..4fb4491e8 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -440,7 +440,13 @@ export async function getReplyFromConfig( "on") : "off"; const resolvedBlockStreaming = - agentCfg?.blockStreamingDefault === "off" ? "off" : "on"; + opts?.disableBlockStreaming === true + ? "off" + : opts?.disableBlockStreaming === false + ? "on" + : agentCfg?.blockStreamingDefault === "off" + ? "off" + : "on"; const resolvedBlockStreamingBreak: "text_end" | "message_end" = agentCfg?.blockStreamingBreak === "message_end" ? "message_end" diff --git a/src/config/types.ts b/src/config/types.ts index 0ff089b44..7cf3775e3 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -125,6 +125,8 @@ export type WhatsAppConfig = { groupPolicy?: GroupPolicy; /** Outbound text chunk size (chars). Default: 4000. */ textChunkLimit?: number; + /** Disable block streaming for this account. */ + blockStreaming?: boolean; /** Per-action tool gating (default: true for all). */ actions?: WhatsAppActionConfig; groups?: Record< @@ -150,6 +152,7 @@ export type WhatsAppAccountConfig = { groupAllowFrom?: string[]; groupPolicy?: GroupPolicy; textChunkLimit?: number; + blockStreaming?: boolean; groups?: Record< string, { @@ -301,6 +304,8 @@ export type TelegramAccountConfig = { groupPolicy?: GroupPolicy; /** Outbound text chunk size (chars). Default: 4000. */ textChunkLimit?: number; + /** Disable block streaming for this account. */ + blockStreaming?: boolean; /** Draft streaming mode for Telegram (off|partial|block). Default: partial. */ streamMode?: "off" | "partial" | "block"; mediaMaxMb?: number; @@ -422,6 +427,8 @@ export type DiscordAccountConfig = { groupPolicy?: GroupPolicy; /** Outbound text chunk size (chars). Default: 2000. */ textChunkLimit?: number; + /** Disable block streaming for this account. */ + blockStreaming?: boolean; /** * Soft max line count per Discord message. * Discord clients can clip/collapse very tall messages; splitting by lines @@ -517,6 +524,7 @@ export type SlackAccountConfig = { */ groupPolicy?: GroupPolicy; textChunkLimit?: number; + blockStreaming?: boolean; mediaMaxMb?: number; /** Reaction notification mode (off|own|all|allowlist). Default: own. */ reactionNotifications?: SlackReactionNotificationMode; @@ -570,6 +578,7 @@ export type SignalAccountConfig = { groupPolicy?: GroupPolicy; /** Outbound text chunk size (chars). Default: 4000. */ textChunkLimit?: number; + blockStreaming?: boolean; mediaMaxMb?: number; }; @@ -668,6 +677,7 @@ export type IMessageAccountConfig = { mediaMaxMb?: number; /** Outbound text chunk size (chars). Default: 4000. */ textChunkLimit?: number; + blockStreaming?: boolean; groups?: Record< string, { diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 995ba8da8..3e1990166 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -191,6 +191,7 @@ const TelegramAccountSchemaBase = z.object({ groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupPolicy: GroupPolicySchema.optional().default("open"), textChunkLimit: z.number().int().positive().optional(), + blockStreaming: z.boolean().optional(), streamMode: z.enum(["off", "partial", "block"]).optional().default("partial"), mediaMaxMb: z.number().positive().optional(), retry: RetryConfigSchema, @@ -275,6 +276,7 @@ const DiscordAccountSchema = z.object({ token: z.string().optional(), groupPolicy: GroupPolicySchema.optional().default("open"), textChunkLimit: z.number().int().positive().optional(), + blockStreaming: z.boolean().optional(), maxLinesPerMessage: z.number().int().positive().optional(), mediaMaxMb: z.number().positive().optional(), historyLimit: z.number().int().min(0).optional(), @@ -344,6 +346,7 @@ const SlackAccountSchema = z.object({ allowBots: z.boolean().optional(), groupPolicy: GroupPolicySchema.optional().default("open"), textChunkLimit: z.number().int().positive().optional(), + blockStreaming: z.boolean().optional(), mediaMaxMb: z.number().positive().optional(), reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(), reactionAllowlist: z.array(z.union([z.string(), z.number()])).optional(), @@ -394,6 +397,7 @@ const SignalAccountSchemaBase = z.object({ groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupPolicy: GroupPolicySchema.optional().default("open"), textChunkLimit: z.number().int().positive().optional(), + blockStreaming: z.boolean().optional(), mediaMaxMb: z.number().int().positive().optional(), }); @@ -438,6 +442,7 @@ const IMessageAccountSchemaBase = z.object({ includeAttachments: z.boolean().optional(), mediaMaxMb: z.number().int().positive().optional(), textChunkLimit: z.number().int().positive().optional(), + blockStreaming: z.boolean().optional(), groups: z .record( z.string(), @@ -1209,6 +1214,7 @@ export const ClawdbotSchema = z.object({ groupAllowFrom: z.array(z.string()).optional(), groupPolicy: GroupPolicySchema.optional().default("open"), textChunkLimit: z.number().int().positive().optional(), + blockStreaming: z.boolean().optional(), groups: z .record( z.string(), @@ -1242,6 +1248,7 @@ export const ClawdbotSchema = z.object({ groupAllowFrom: z.array(z.string()).optional(), groupPolicy: GroupPolicySchema.optional().default("open"), textChunkLimit: z.number().int().positive().optional(), + blockStreaming: z.boolean().optional(), actions: z .object({ reactions: z.boolean().optional(), diff --git a/src/discord/monitor.ts b/src/discord/monitor.ts index c3339ef66..0e66c9e37 100644 --- a/src/discord/monitor.ts +++ b/src/discord/monitor.ts @@ -1150,7 +1150,14 @@ export function createDiscordMessageHandler(params: { ctx: ctxPayload, cfg, dispatcher, - replyOptions: { ...replyOptions, skillFilter: channelConfig?.skills }, + replyOptions: { + ...replyOptions, + skillFilter: channelConfig?.skills, + disableBlockStreaming: + typeof discordConfig?.blockStreaming === "boolean" + ? !discordConfig.blockStreaming + : undefined, + }, }); markDispatchIdle(); if (!queuedFinal) { diff --git a/src/imessage/monitor.ts b/src/imessage/monitor.ts index 48f058846..67af17e55 100644 --- a/src/imessage/monitor.ts +++ b/src/imessage/monitor.ts @@ -466,6 +466,12 @@ export async function monitorIMessageProvider( ctx: ctxPayload, cfg, dispatcher, + replyOptions: { + disableBlockStreaming: + typeof accountInfo.config.blockStreaming === "boolean" + ? !accountInfo.config.blockStreaming + : undefined, + }, }); if (!queuedFinal) return; }; diff --git a/src/signal/monitor.ts b/src/signal/monitor.ts index 89bef0060..b483ddced 100644 --- a/src/signal/monitor.ts +++ b/src/signal/monitor.ts @@ -533,6 +533,12 @@ export async function monitorSignalProvider( ctx: ctxPayload, cfg, dispatcher, + replyOptions: { + disableBlockStreaming: + typeof accountInfo.config.blockStreaming === "boolean" + ? !accountInfo.config.blockStreaming + : undefined, + }, }); if (!queuedFinal) return; }; diff --git a/src/slack/monitor.ts b/src/slack/monitor.ts index f19b1bb70..ea02865a9 100644 --- a/src/slack/monitor.ts +++ b/src/slack/monitor.ts @@ -1116,7 +1116,14 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { ctx: ctxPayload, cfg, dispatcher, - replyOptions: { ...replyOptions, skillFilter: channelConfig?.skills }, + replyOptions: { + ...replyOptions, + skillFilter: channelConfig?.skills, + disableBlockStreaming: + typeof account.config.blockStreaming === "boolean" + ? !account.config.blockStreaming + : undefined, + }, }); markDispatchIdle(); if (didSetStatus) { diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index 52461ef59..c0226a4c8 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -770,7 +770,11 @@ export function createTelegramBot(opts: TelegramBotOptions) { if (payload.text) draftStream.update(payload.text); } : undefined, - disableBlockStreaming: Boolean(draftStream), + disableBlockStreaming: + Boolean(draftStream) || + (typeof telegramCfg.blockStreaming === "boolean" + ? !telegramCfg.blockStreaming + : undefined), }, }); markDispatchIdle(); diff --git a/src/web/accounts.ts b/src/web/accounts.ts index b09733b28..5a368a2d1 100644 --- a/src/web/accounts.ts +++ b/src/web/accounts.ts @@ -23,6 +23,7 @@ export type ResolvedWhatsAppAccount = { groupPolicy?: GroupPolicy; dmPolicy?: DmPolicy; textChunkLimit?: number; + blockStreaming?: boolean; groups?: WhatsAppAccountConfig["groups"]; }; @@ -119,6 +120,8 @@ export function resolveWhatsAppAccount(params: { groupPolicy: accountCfg?.groupPolicy ?? params.cfg.whatsapp?.groupPolicy, textChunkLimit: accountCfg?.textChunkLimit ?? params.cfg.whatsapp?.textChunkLimit, + blockStreaming: + accountCfg?.blockStreaming ?? params.cfg.whatsapp?.blockStreaming, groups: accountCfg?.groups ?? params.cfg.whatsapp?.groups, }; } diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index 19bae97d2..3764148cf 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -788,6 +788,7 @@ export async function monitorWebProvider( groupAllowFrom: account.groupAllowFrom, groupPolicy: account.groupPolicy, textChunkLimit: account.textChunkLimit, + blockStreaming: account.blockStreaming, groups: account.groups, }, } satisfies ReturnType; @@ -1276,7 +1277,13 @@ export async function monitorWebProvider( cfg, dispatcher, replyResolver, - replyOptions, + replyOptions: { + ...replyOptions, + disableBlockStreaming: + typeof cfg.whatsapp?.blockStreaming === "boolean" + ? !cfg.whatsapp.blockStreaming + : undefined, + }, }); markDispatchIdle(); if (!queuedFinal) {