feat: make streaming edits toggleable per-channel, disabled by default (#436)
This commit is contained in:
@@ -65,17 +65,20 @@ channels:
|
|||||||
enabled: true
|
enabled: true
|
||||||
token: "123456:ABC-DEF..."
|
token: "123456:ABC-DEF..."
|
||||||
dmPolicy: pairing
|
dmPolicy: pairing
|
||||||
|
# streaming: true # Opt-in: progressively edit messages as tokens arrive
|
||||||
|
|
||||||
slack:
|
slack:
|
||||||
enabled: true
|
enabled: true
|
||||||
botToken: xoxb-...
|
botToken: xoxb-...
|
||||||
appToken: xapp-...
|
appToken: xapp-...
|
||||||
dmPolicy: pairing
|
dmPolicy: pairing
|
||||||
|
# streaming: true
|
||||||
|
|
||||||
discord:
|
discord:
|
||||||
enabled: true
|
enabled: true
|
||||||
token: "..."
|
token: "..."
|
||||||
dmPolicy: pairing
|
dmPolicy: pairing
|
||||||
|
# streaming: true
|
||||||
|
|
||||||
whatsapp:
|
whatsapp:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -368,6 +371,7 @@ All channels share these common options:
|
|||||||
| `instantGroups` | string[] | Group/channel IDs that bypass debounce entirely (legacy) |
|
| `instantGroups` | string[] | Group/channel IDs that bypass debounce entirely (legacy) |
|
||||||
| `groups` | object | Per-group configuration map (use `*` as default) |
|
| `groups` | object | Per-group configuration map (use `*` as default) |
|
||||||
| `mentionPatterns` | string[] | Extra regex patterns for mention detection (Telegram/WhatsApp/Signal) |
|
| `mentionPatterns` | string[] | Extra regex patterns for mention detection (Telegram/WhatsApp/Signal) |
|
||||||
|
| `streaming` | boolean | Stream responses via progressive message edits (default: false; Telegram/Discord/Slack only) |
|
||||||
|
|
||||||
### Group Message Debouncing
|
### Group Message Debouncing
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface DiscordConfig {
|
|||||||
token: string;
|
token: string;
|
||||||
dmPolicy?: DmPolicy; // 'pairing' (default), 'allowlist', or 'open'
|
dmPolicy?: DmPolicy; // 'pairing' (default), 'allowlist', or 'open'
|
||||||
allowedUsers?: string[]; // Discord user IDs
|
allowedUsers?: string[]; // Discord user IDs
|
||||||
|
streaming?: boolean; // Stream responses via progressive message edits (default: false)
|
||||||
attachmentsDir?: string;
|
attachmentsDir?: string;
|
||||||
attachmentsMaxBytes?: number;
|
attachmentsMaxBytes?: number;
|
||||||
groups?: Record<string, GroupModeConfig>; // Per-guild/channel settings
|
groups?: Record<string, GroupModeConfig>; // Per-guild/channel settings
|
||||||
@@ -414,7 +415,7 @@ Ask the bot owner to approve with:
|
|||||||
}
|
}
|
||||||
|
|
||||||
supportsEditing(): boolean {
|
supportsEditing(): boolean {
|
||||||
return true;
|
return this.config.streaming ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleReactionEvent(
|
private async handleReactionEvent(
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface SlackConfig {
|
|||||||
appToken: string; // xapp-... (for Socket Mode)
|
appToken: string; // xapp-... (for Socket Mode)
|
||||||
dmPolicy?: 'pairing' | 'allowlist' | 'open';
|
dmPolicy?: 'pairing' | 'allowlist' | 'open';
|
||||||
allowedUsers?: string[]; // Slack user IDs (e.g., U01234567)
|
allowedUsers?: string[]; // Slack user IDs (e.g., U01234567)
|
||||||
|
streaming?: boolean; // Stream responses via progressive message edits (default: false)
|
||||||
attachmentsDir?: string;
|
attachmentsDir?: string;
|
||||||
attachmentsMaxBytes?: number;
|
attachmentsMaxBytes?: number;
|
||||||
groups?: Record<string, GroupModeConfig>; // Per-channel settings
|
groups?: Record<string, GroupModeConfig>; // Per-channel settings
|
||||||
@@ -326,6 +327,10 @@ export class SlackAdapter implements ChannelAdapter {
|
|||||||
return { messageId: ts };
|
return { messageId: ts };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supportsEditing(): boolean {
|
||||||
|
return this.config.streaming ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
async editMessage(chatId: string, messageId: string, text: string): Promise<void> {
|
async editMessage(chatId: string, messageId: string, text: string): Promise<void> {
|
||||||
if (!this.app) throw new Error('Slack not started');
|
if (!this.app) throw new Error('Slack not started');
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface TelegramConfig {
|
|||||||
token: string;
|
token: string;
|
||||||
dmPolicy?: DmPolicy; // 'pairing' (default), 'allowlist', or 'open'
|
dmPolicy?: DmPolicy; // 'pairing' (default), 'allowlist', or 'open'
|
||||||
allowedUsers?: number[]; // Telegram user IDs (config allowlist)
|
allowedUsers?: number[]; // Telegram user IDs (config allowlist)
|
||||||
|
streaming?: boolean; // Stream responses via progressive message edits (default: false)
|
||||||
attachmentsDir?: string;
|
attachmentsDir?: string;
|
||||||
attachmentsMaxBytes?: number;
|
attachmentsMaxBytes?: number;
|
||||||
mentionPatterns?: string[]; // Regex patterns for mention detection
|
mentionPatterns?: string[]; // Regex patterns for mention detection
|
||||||
@@ -600,6 +601,10 @@ export class TelegramAdapter implements ChannelAdapter {
|
|||||||
return { messageId: String(result.message_id) };
|
return { messageId: String(result.message_id) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supportsEditing(): boolean {
|
||||||
|
return this.config.streaming ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
async editMessage(chatId: string, messageId: string, text: string): Promise<void> {
|
async editMessage(chatId: string, messageId: string, text: string): Promise<void> {
|
||||||
const { markdownToTelegramV2 } = await import('./telegram-format.js');
|
const { markdownToTelegramV2 } = await import('./telegram-format.js');
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ export interface TelegramConfig {
|
|||||||
token?: string;
|
token?: string;
|
||||||
dmPolicy?: 'pairing' | 'allowlist' | 'open';
|
dmPolicy?: 'pairing' | 'allowlist' | 'open';
|
||||||
allowedUsers?: string[];
|
allowedUsers?: string[];
|
||||||
|
streaming?: boolean; // Stream responses via progressive message edits (default: false)
|
||||||
groupDebounceSec?: number; // Debounce interval in seconds (default: 5, 0 = immediate)
|
groupDebounceSec?: number; // Debounce interval in seconds (default: 5, 0 = immediate)
|
||||||
groupPollIntervalMin?: number; // @deprecated Use groupDebounceSec instead
|
groupPollIntervalMin?: number; // @deprecated Use groupDebounceSec instead
|
||||||
instantGroups?: string[]; // Group chat IDs that bypass batching
|
instantGroups?: string[]; // Group chat IDs that bypass batching
|
||||||
@@ -299,6 +300,7 @@ export interface SlackConfig {
|
|||||||
botToken?: string;
|
botToken?: string;
|
||||||
dmPolicy?: 'pairing' | 'allowlist' | 'open';
|
dmPolicy?: 'pairing' | 'allowlist' | 'open';
|
||||||
allowedUsers?: string[];
|
allowedUsers?: string[];
|
||||||
|
streaming?: boolean; // Stream responses via progressive message edits (default: false)
|
||||||
groupDebounceSec?: number; // Debounce interval in seconds (default: 5, 0 = immediate)
|
groupDebounceSec?: number; // Debounce interval in seconds (default: 5, 0 = immediate)
|
||||||
groupPollIntervalMin?: number; // @deprecated Use groupDebounceSec instead
|
groupPollIntervalMin?: number; // @deprecated Use groupDebounceSec instead
|
||||||
instantGroups?: string[]; // Channel IDs that bypass batching
|
instantGroups?: string[]; // Channel IDs that bypass batching
|
||||||
@@ -345,6 +347,7 @@ export interface DiscordConfig {
|
|||||||
token?: string;
|
token?: string;
|
||||||
dmPolicy?: 'pairing' | 'allowlist' | 'open';
|
dmPolicy?: 'pairing' | 'allowlist' | 'open';
|
||||||
allowedUsers?: string[];
|
allowedUsers?: string[];
|
||||||
|
streaming?: boolean; // Stream responses via progressive message edits (default: false)
|
||||||
groupDebounceSec?: number; // Debounce interval in seconds (default: 5, 0 = immediate)
|
groupDebounceSec?: number; // Debounce interval in seconds (default: 5, 0 = immediate)
|
||||||
groupPollIntervalMin?: number; // @deprecated Use groupDebounceSec instead
|
groupPollIntervalMin?: number; // @deprecated Use groupDebounceSec instead
|
||||||
instantGroups?: string[]; // Guild/server IDs or channel IDs that bypass batching
|
instantGroups?: string[]; // Guild/server IDs or channel IDs that bypass batching
|
||||||
|
|||||||
@@ -2082,7 +2082,7 @@ export class LettaBot implements AgentSession {
|
|||||||
|
|
||||||
// Live-edit streaming for channels that support it
|
// Live-edit streaming for channels that support it
|
||||||
// Hold back streaming edits while response could still be <no-reply/> or <actions> block
|
// Hold back streaming edits while response could still be <no-reply/> or <actions> block
|
||||||
const canEdit = adapter.supportsEditing?.() ?? true;
|
const canEdit = adapter.supportsEditing?.() ?? false;
|
||||||
const trimmed = response.trim();
|
const trimmed = response.trim();
|
||||||
const mayBeHidden = '<no-reply/>'.startsWith(trimmed)
|
const mayBeHidden = '<no-reply/>'.startsWith(trimmed)
|
||||||
|| '<actions>'.startsWith(trimmed)
|
|| '<actions>'.startsWith(trimmed)
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ function createChannelsForAgent(
|
|||||||
allowedUsers: agentConfig.channels.telegram!.allowedUsers && agentConfig.channels.telegram!.allowedUsers.length > 0
|
allowedUsers: agentConfig.channels.telegram!.allowedUsers && agentConfig.channels.telegram!.allowedUsers.length > 0
|
||||||
? agentConfig.channels.telegram!.allowedUsers.map(u => typeof u === 'string' ? parseInt(u, 10) : u)
|
? agentConfig.channels.telegram!.allowedUsers.map(u => typeof u === 'string' ? parseInt(u, 10) : u)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
streaming: agentConfig.channels.telegram!.streaming,
|
||||||
attachmentsDir,
|
attachmentsDir,
|
||||||
attachmentsMaxBytes,
|
attachmentsMaxBytes,
|
||||||
groups: agentConfig.channels.telegram!.groups,
|
groups: agentConfig.channels.telegram!.groups,
|
||||||
@@ -396,6 +397,7 @@ function createChannelsForAgent(
|
|||||||
allowedUsers: agentConfig.channels.slack.allowedUsers && agentConfig.channels.slack.allowedUsers.length > 0
|
allowedUsers: agentConfig.channels.slack.allowedUsers && agentConfig.channels.slack.allowedUsers.length > 0
|
||||||
? agentConfig.channels.slack.allowedUsers
|
? agentConfig.channels.slack.allowedUsers
|
||||||
: undefined,
|
: undefined,
|
||||||
|
streaming: agentConfig.channels.slack.streaming,
|
||||||
attachmentsDir,
|
attachmentsDir,
|
||||||
attachmentsMaxBytes,
|
attachmentsMaxBytes,
|
||||||
groups: agentConfig.channels.slack.groups,
|
groups: agentConfig.channels.slack.groups,
|
||||||
@@ -452,6 +454,7 @@ function createChannelsForAgent(
|
|||||||
allowedUsers: agentConfig.channels.discord.allowedUsers && agentConfig.channels.discord.allowedUsers.length > 0
|
allowedUsers: agentConfig.channels.discord.allowedUsers && agentConfig.channels.discord.allowedUsers.length > 0
|
||||||
? agentConfig.channels.discord.allowedUsers
|
? agentConfig.channels.discord.allowedUsers
|
||||||
: undefined,
|
: undefined,
|
||||||
|
streaming: agentConfig.channels.discord.streaming,
|
||||||
attachmentsDir,
|
attachmentsDir,
|
||||||
attachmentsMaxBytes,
|
attachmentsMaxBytes,
|
||||||
groups: agentConfig.channels.discord.groups,
|
groups: agentConfig.channels.discord.groups,
|
||||||
|
|||||||
Reference in New Issue
Block a user