mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix(telegram): warn on selected quote tool progress
This commit is contained in:
@@ -25,6 +25,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Control UI/Sessions: avoid full `sessions.list` reloads for chat-turn `sessions.changed` payloads, so large session stores no longer add multi-second delays while chat responses are being delivered. (#76676) Thanks @VACInc.
|
||||
- Doctor/Telegram: warn when selected Telegram quote replies can suppress `streaming.preview.toolProgress`, and document the `replyToMode` trade-off without changing runtime delivery. Fixes #73487. Thanks @GodsBoy.
|
||||
- Discord/status: honor explicit `messages.statusReactions.enabled: true` in tool-only guild channels so queued ack reactions can progress through thinking/done lifecycle reactions instead of stopping at the initial emoji. Thanks @Marvinthebored.
|
||||
- Discord/native commands: compare Discord-normalized slash-command descriptions and localized descriptions during reconcile so CJK or multiline command text no longer triggers redundant startup PATCH bursts and rate-limit 429s. Fixes #76587. Thanks @zhengsx.
|
||||
- Agents/OpenAI: omit Chat Completions `reasoning_effort` for `gpt-5.4-mini` only when function tools are present while preserving tool-free Chat and Responses reasoning support, preventing Telegram-routed fallback runs from hanging after OpenAI rejects tool payloads. Fixes #76176. Thanks @ThisIsAdilah and @chinar-amrutkar.
|
||||
|
||||
@@ -302,7 +302,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
Use `streaming.mode: "off"` only when you want final-only delivery: Telegram preview edits are disabled and generic tool/progress chatter is suppressed instead of being sent as standalone "Working..." messages. Approval prompts, media payloads, and errors still route through normal final delivery. Use `streaming.preview.toolProgress: false` when you only want to keep answer preview edits while hiding the tool-progress status lines.
|
||||
|
||||
<Note>
|
||||
`streaming.preview.toolProgress` requires `channels.telegram.replyToMode: "off"`. When quote-reply is enabled (`replyToMode: "first"`, `"all"`, or `"batched"`), Telegram requires the final message reference at send time, which is incompatible with preview-edit streaming. The two features are mutually exclusive: tool-progress lines cannot appear in the same preview message that will later be replaced by a quoted final reply. To restore tool-progress visibility, set `replyToMode: "off"`. To suppress the warning while keeping quote-reply, set `streaming.preview.toolProgress: false` to acknowledge the trade-off.
|
||||
Telegram selected quote replies are the exception. When `replyToMode` is `"first"`, `"all"`, or `"batched"` and the inbound message includes selected quote text, OpenClaw sends the final answer through Telegram's native quote-reply path instead of editing the answer preview, so `streaming.preview.toolProgress` cannot show the short "Working..." lines for that turn. Current-message replies without selected quote text still keep preview streaming. Set `replyToMode: "off"` when tool-progress visibility matters more than native quote replies, or set `streaming.preview.toolProgress: false` to acknowledge the trade-off.
|
||||
</Note>
|
||||
|
||||
For text-only replies:
|
||||
|
||||
@@ -194,7 +194,7 @@ Supported surfaces:
|
||||
- **Mattermost** already folds tool activity into its single draft preview post (see above).
|
||||
- Tool-progress edits follow the active preview streaming mode; they are skipped when preview streaming is `off` or when block streaming has taken over the message. On Telegram, `streaming.mode: "off"` is final-only: generic progress chatter is also suppressed instead of being delivered as standalone "Working..." messages, while approval prompts, media payloads, and errors still route normally.
|
||||
- To keep preview streaming but hide tool-progress lines, set `streaming.preview.toolProgress` to `false` for that channel. To disable preview edits entirely, set `streaming.mode` to `off`.
|
||||
- On Telegram specifically, `streaming.preview.toolProgress` requires `channels.telegram.replyToMode: "off"`. Quote-reply needs the final message reference at send time, which is incompatible with preview-edit streaming, so the two are mutually exclusive. See [Telegram channel docs](/channels/telegram) for the full note.
|
||||
- Telegram selected quote replies are an exception: when `replyToMode` is not `"off"` and selected quote text is present, OpenClaw skips the answer preview stream for that turn so tool-progress preview lines cannot render. Current-message replies without selected quote text still keep preview streaming. See [Telegram channel docs](/channels/telegram) for details.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@ import {
|
||||
collectTelegramEmptyAllowlistExtraWarnings,
|
||||
collectTelegramGroupPolicyWarnings,
|
||||
collectTelegramMissingEnvTokenWarnings,
|
||||
collectTelegramSelectedQuoteToolProgressWarnings,
|
||||
maybeRepairTelegramApiRoots,
|
||||
maybeRepairTelegramAllowFromUsernames,
|
||||
scanTelegramBotEndpointApiRoots,
|
||||
scanTelegramInvalidAllowFromEntries,
|
||||
scanTelegramSelectedQuoteToolProgressWarnings,
|
||||
telegramDoctor,
|
||||
} from "./doctor.js";
|
||||
|
||||
@@ -329,6 +331,112 @@ describe("telegram doctor", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("warns when selected quote replies can suppress Telegram tool-progress preview", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
replyToMode: "first",
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const hits = scanTelegramSelectedQuoteToolProgressWarnings(cfg);
|
||||
expect(hits).toEqual([{ path: "channels.telegram", replyToMode: "first" }]);
|
||||
|
||||
const warnings = collectTelegramSelectedQuoteToolProgressWarnings({ hits });
|
||||
expect(warnings[0]).toContain("selected quote replies");
|
||||
expect(warnings[0]).toContain('"Working..." tool-progress preview');
|
||||
expect(warnings[0]).toContain("Current-message replies without selected quote text");
|
||||
expect(warnings[1]).toContain("streaming.preview.toolProgress: false");
|
||||
expect(
|
||||
await telegramDoctor.collectPreviewWarnings?.({
|
||||
cfg,
|
||||
doctorFixCommand: "openclaw doctor --fix",
|
||||
}),
|
||||
).toEqual(expect.arrayContaining([expect.stringContaining("selected quote replies")]));
|
||||
});
|
||||
|
||||
it("warns for the implicit default Telegram account when accounts is empty", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
replyToMode: "all",
|
||||
accounts: {},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expect(scanTelegramSelectedQuoteToolProgressWarnings(cfg)).toEqual([
|
||||
{ path: "channels.telegram", replyToMode: "all" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses merged Telegram account config for selected quote tool-progress warnings", () => {
|
||||
listTelegramAccountIdsMock.mockReturnValue(["work", "quiet"]);
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
replyToMode: "batched",
|
||||
accounts: {
|
||||
work: {},
|
||||
quiet: {
|
||||
replyToMode: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expect(scanTelegramSelectedQuoteToolProgressWarnings(cfg)).toEqual([
|
||||
{ path: "channels.telegram.accounts.work", replyToMode: "batched" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("skips selected quote tool-progress warning when preview progress is disabled", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
replyToMode: "first",
|
||||
streaming: {
|
||||
preview: {
|
||||
toolProgress: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expect(scanTelegramSelectedQuoteToolProgressWarnings(cfg)).toEqual([]);
|
||||
});
|
||||
|
||||
it("skips selected quote tool-progress warning when preview streaming is off or block streaming owns delivery", () => {
|
||||
expect(
|
||||
scanTelegramSelectedQuoteToolProgressWarnings({
|
||||
channels: {
|
||||
telegram: {
|
||||
replyToMode: "first",
|
||||
streaming: false,
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig),
|
||||
).toEqual([]);
|
||||
|
||||
expect(
|
||||
scanTelegramSelectedQuoteToolProgressWarnings({
|
||||
channels: {
|
||||
telegram: {
|
||||
replyToMode: "first",
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
blockStreamingDefault: "on",
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("wires apiRoot preview warnings and repair through the doctor adapter", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
|
||||
@@ -2,12 +2,17 @@ import {
|
||||
type ChannelDoctorAdapter,
|
||||
type ChannelDoctorEmptyAllowlistAccountContext,
|
||||
} from "openclaw/plugin-sdk/channel-contract";
|
||||
import {
|
||||
resolveChannelStreamingBlockEnabled,
|
||||
resolveChannelStreamingPreviewToolProgress,
|
||||
} from "openclaw/plugin-sdk/channel-streaming";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { inspectTelegramAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
mergeTelegramAccountConfig,
|
||||
resolveDefaultTelegramAccountId,
|
||||
resolveTelegramAccount,
|
||||
} from "./accounts.js";
|
||||
@@ -18,8 +23,10 @@ import {
|
||||
legacyConfigRules as TELEGRAM_LEGACY_CONFIG_RULES,
|
||||
normalizeCompatibilityConfig as normalizeTelegramCompatibilityConfig,
|
||||
} from "./doctor-contract.js";
|
||||
import { resolveTelegramPreviewStreamMode } from "./preview-streaming.js";
|
||||
|
||||
type TelegramAllowFromInvalidHit = { path: string; entry: string };
|
||||
type TelegramSelectedQuoteToolProgressHit = { path: string; replyToMode: string };
|
||||
type TelegramApiRootBotEndpointHit = {
|
||||
path: string;
|
||||
pathSegments: string[];
|
||||
@@ -196,6 +203,58 @@ export function collectTelegramApiRootWarnings(params: {
|
||||
];
|
||||
}
|
||||
|
||||
function formatTelegramAccountConfigPath(cfg: OpenClawConfig, accountId: string): string {
|
||||
const telegram = asObjectRecord((cfg.channels as Record<string, unknown> | undefined)?.telegram);
|
||||
const accounts = asObjectRecord(telegram?.accounts);
|
||||
if (!accounts || Object.keys(accounts).length === 0) {
|
||||
return "channels.telegram";
|
||||
}
|
||||
return accountId === "default" ? "channels.telegram" : `channels.telegram.accounts.${accountId}`;
|
||||
}
|
||||
|
||||
export function scanTelegramSelectedQuoteToolProgressWarnings(
|
||||
cfg: OpenClawConfig,
|
||||
): TelegramSelectedQuoteToolProgressHit[] {
|
||||
if (!asObjectRecord((cfg.channels as Record<string, unknown> | undefined)?.telegram)) {
|
||||
return [];
|
||||
}
|
||||
return listTelegramAccountIds(cfg).flatMap((accountId) => {
|
||||
const account = mergeTelegramAccountConfig(cfg, accountId);
|
||||
const replyToMode = account.replyToMode ?? "off";
|
||||
if (replyToMode === "off") {
|
||||
return [];
|
||||
}
|
||||
if (resolveTelegramPreviewStreamMode(account) === "off") {
|
||||
return [];
|
||||
}
|
||||
const blockStreamingEnabled =
|
||||
resolveChannelStreamingBlockEnabled(account) ??
|
||||
cfg.agents?.defaults?.blockStreamingDefault === "on";
|
||||
if (blockStreamingEnabled || !resolveChannelStreamingPreviewToolProgress(account)) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
path: formatTelegramAccountConfigPath(cfg, accountId),
|
||||
replyToMode,
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
export function collectTelegramSelectedQuoteToolProgressWarnings(params: {
|
||||
hits: TelegramSelectedQuoteToolProgressHit[];
|
||||
}): string[] {
|
||||
if (params.hits.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const sample = params.hits[0] ?? { path: "channels.telegram", replyToMode: "first" };
|
||||
return [
|
||||
`- ${sanitizeForLog(sample.path)} has replyToMode: "${sanitizeForLog(sample.replyToMode)}" while Telegram preview tool-progress is enabled. Telegram selected quote replies must send the final answer through the native quote-reply path, so those turns skip the short "Working..." tool-progress preview. Current-message replies without selected quote text still keep preview streaming.`,
|
||||
'- Set replyToMode: "off" when tool-progress preview matters more than native quote replies, or set streaming.preview.toolProgress: false to keep quote replies and silence this warning.',
|
||||
];
|
||||
}
|
||||
|
||||
export function maybeRepairTelegramApiRoots(cfg: OpenClawConfig): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
@@ -506,6 +565,9 @@ export const telegramDoctor: ChannelDoctorAdapter = {
|
||||
hits: scanTelegramBotEndpointApiRoots(cfg),
|
||||
doctorFixCommand,
|
||||
}),
|
||||
...collectTelegramSelectedQuoteToolProgressWarnings({
|
||||
hits: scanTelegramSelectedQuoteToolProgressWarnings(cfg),
|
||||
}),
|
||||
],
|
||||
repairConfig: async ({ cfg }) => await repairTelegramConfig({ cfg }),
|
||||
collectEmptyAllowlistExtraWarnings: collectTelegramEmptyAllowlistExtraWarnings,
|
||||
|
||||
Reference in New Issue
Block a user