From a3bc0097c8184e9ee5b332251156649fa20195d0 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sat, 13 Jun 2026 17:59:58 +0530 Subject: [PATCH] fix(telegram): migrate retired native draft config --- docs/channels/telegram.md | 2 +- extensions/telegram/src/doctor-contract.ts | 81 ++++++++++++++++++++++ extensions/telegram/src/doctor.test.ts | 46 ++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md index c28ccc1287fc..69f4cf492687 100644 --- a/docs/channels/telegram.md +++ b/docs/channels/telegram.md @@ -319,7 +319,7 @@ curl "https://api.telegram.org/bot/getUpdates" - `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true` when preview streaming is active) - `streaming.preview.commandText` controls command/exec detail inside those tool-progress lines: `raw` (default, preserves released behavior) or `status` (tool label only) - `streaming.progress.commentary` (default: `false`) opts into assistant commentary/preamble text in the temporary progress draft - - legacy `channels.telegram.streamMode` and boolean `streaming` values are detected; run `openclaw doctor --fix` to migrate them to `channels.telegram.streaming.mode` + - legacy `channels.telegram.streamMode`, boolean `streaming` values, and retired native draft preview keys are detected; run `openclaw doctor --fix` to migrate them to current streaming config Tool-progress preview updates are the short status lines shown while tools run, for example command execution, file reads, planning updates, patch summaries, or Codex preamble/commentary text in Codex app-server mode. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later. diff --git a/extensions/telegram/src/doctor-contract.ts b/extensions/telegram/src/doctor-contract.ts index ffb910f114bb..a3d2454feb8e 100644 --- a/extensions/telegram/src/doctor-contract.ts +++ b/extensions/telegram/src/doctor-contract.ts @@ -37,6 +37,23 @@ function hasRetiredTelegramAccountDmConfig(value: unknown): boolean { return Object.values(accounts).some((account) => hasRetiredTelegramDmConfig(account)); } +function hasRetiredTelegramNativeDraftConfig(value: unknown): boolean { + const entry = asObjectRecord(value); + const streaming = asObjectRecord(entry?.streaming); + const preview = asObjectRecord(streaming?.preview); + return ( + preview?.nativeToolProgress !== undefined || preview?.nativeToolProgressAllowFrom !== undefined + ); +} + +function hasRetiredTelegramAccountNativeDraftConfig(value: unknown): boolean { + const accounts = asObjectRecord(value); + if (!accounts) { + return false; + } + return Object.values(accounts).some((account) => hasRetiredTelegramNativeDraftConfig(account)); +} + function removeRetiredTelegramDmConfig(params: { entry: Record; pathPrefix: string; @@ -82,6 +99,41 @@ function removeRetiredTelegramDmConfig(params: { return { entry: updated, changed }; } +function removeRetiredTelegramNativeDraftConfig(params: { + entry: Record; + pathPrefix: string; + changes: string[]; +}): { entry: Record; changed: boolean } { + const streaming = asObjectRecord(params.entry.streaming); + const preview = asObjectRecord(streaming?.preview); + if ( + !streaming || + !preview || + (preview.nativeToolProgress === undefined && preview.nativeToolProgressAllowFrom === undefined) + ) { + return { entry: params.entry, changed: false }; + } + + const nextPreview = { ...preview }; + delete nextPreview.nativeToolProgress; + delete nextPreview.nativeToolProgressAllowFrom; + const nextStreaming = { ...streaming }; + if (Object.keys(nextPreview).length > 0) { + nextStreaming.preview = nextPreview; + } else { + delete nextStreaming.preview; + } + + const updated = + Object.keys(nextStreaming).length > 0 + ? { ...params.entry, streaming: nextStreaming } + : Object.fromEntries(Object.entries(params.entry).filter(([key]) => key !== "streaming")); + params.changes.push( + `Removed ${params.pathPrefix}.streaming.preview native draft keys; Telegram previews now use rich send/edit messages.`, + ); + return { entry: updated, changed: true }; +} + function resolveCompatibleDefaultGroupEntry(section: Record): { groups: Record; entry: Record; @@ -118,6 +170,18 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [ 'channels.telegram.accounts..dm and direct..threadReplies were removed; DM topic sessions now follow Telegram getMe.has_topics_enabled, so topics-enabled bots may use thread-scoped DM sessions. Run "openclaw doctor --fix".', match: hasRetiredTelegramAccountDmConfig, }, + { + path: ["channels", "telegram"], + message: + 'channels.telegram.streaming.preview.nativeToolProgress and nativeToolProgressAllowFrom were removed; Telegram previews now use rich send/edit messages. Run "openclaw doctor --fix".', + match: hasRetiredTelegramNativeDraftConfig, + }, + { + path: ["channels", "telegram", "accounts"], + message: + 'channels.telegram.accounts..streaming.preview.nativeToolProgress and nativeToolProgressAllowFrom were removed; Telegram previews now use rich send/edit messages. Run "openclaw doctor --fix".', + match: hasRetiredTelegramAccountNativeDraftConfig, + }, { path: ["channels", "telegram"], message: @@ -154,6 +218,14 @@ export function normalizeCompatibilityConfig({ updated = removedThreadReplies.entry; changed = changed || removedThreadReplies.changed; + const removedNativeDraft = removeRetiredTelegramNativeDraftConfig({ + entry: updated, + pathPrefix: "channels.telegram", + changes, + }); + updated = removedNativeDraft.entry; + changed = changed || removedNativeDraft.changed; + if (updated.groupMentionsOnly !== undefined) { const defaultGroupEntry = resolveCompatibleDefaultGroupEntry(updated); if (!defaultGroupEntry) { @@ -210,6 +282,15 @@ export function normalizeCompatibilityConfig({ nextAccounts[accountId] = accountRemovedThreadReplies.entry; accountsChanged = true; } + const accountRemovedNativeDraft = removeRetiredTelegramNativeDraftConfig({ + entry: nextAccounts[accountId] as Record, + pathPrefix: `channels.telegram.accounts.${accountId}`, + changes, + }); + if (accountRemovedNativeDraft.changed) { + nextAccounts[accountId] = accountRemovedNativeDraft.entry; + accountsChanged = true; + } } if (accountsChanged) { updated = { ...updated, accounts: nextAccounts }; diff --git a/extensions/telegram/src/doctor.test.ts b/extensions/telegram/src/doctor.test.ts index 50cd93bbc6bf..18a0a753a986 100644 --- a/extensions/telegram/src/doctor.test.ts +++ b/extensions/telegram/src/doctor.test.ts @@ -229,6 +229,52 @@ describe("telegram doctor", () => { ]); }); + it("removes retired native draft preview keys", () => { + const normalize = telegramDoctor.normalizeCompatibilityConfig; + if (!normalize) { + throw new Error("expected telegram compatibility normalizer"); + } + + const result = normalize({ + cfg: { + channels: { + telegram: { + streaming: { + mode: "partial", + preview: { + toolProgress: true, + nativeToolProgress: true, + nativeToolProgressAllowFrom: ["123"], + }, + }, + accounts: { + work: { + streaming: { + preview: { + nativeToolProgress: true, + }, + }, + }, + }, + }, + }, + } as never, + }); + + const telegram = result.config.channels?.telegram; + expect(telegram?.streaming).toEqual({ + mode: "partial", + preview: { + toolProgress: true, + }, + }); + expect(telegram?.accounts?.work?.streaming).toBeUndefined(); + expect(result.changes).toEqual([ + "Removed channels.telegram.streaming.preview native draft keys; Telegram previews now use rich send/edit messages.", + "Removed channels.telegram.accounts.work.streaming.preview native draft keys; Telegram previews now use rich send/edit messages.", + ]); + }); + it("finds invalid allowFrom entries across scopes", () => { const hits = scanTelegramInvalidAllowFromEntries({ channels: {