惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
aimingoo的专栏
aimingoo的专栏
WordPress大学
WordPress大学
G
Google Developers Blog
博客园 - 叶小钗
大猫的无限游戏
大猫的无限游戏
P
Proofpoint News Feed
J
Java Code Geeks
U
Unit 42
云风的 BLOG
云风的 BLOG
阮一峰的网络日志
阮一峰的网络日志
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
Docker
V
Visual Studio Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Help Net Security
V
V2EX
T
Tailwind CSS Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix: harden telegram localized command menus (#81351) (th...
steipete · 2026-05-13 · via Recent Commits to openclaw:main

@@ -14,6 +14,7 @@ const TELEGRAM_MAX_COMMANDS = 100;

1414

export const TELEGRAM_TOTAL_COMMAND_TEXT_BUDGET = 5700;

1515

const TELEGRAM_COMMAND_RETRY_RATIO = 0.8;

1616

const TELEGRAM_MIN_COMMAND_DESCRIPTION_LENGTH = 1;

17+

const TELEGRAM_MAX_COMMAND_DESCRIPTION_LENGTH = 256;

1718

const TELEGRAM_MENU_RESULT_CACHE_MAX = 128;

18191920

export type TelegramMenuCommand = {

@@ -101,7 +102,10 @@ function fitTelegramCommandsWithinTextBudget(

101102

);

102103

let descriptionTrimmed = false;

103104

const fittedCommands = candidateCommands.map((command) => {

104-

const description = truncateTelegramCommandText(command.description, descriptionCap);

105+

const description = truncateTelegramCommandText(

106+

command.description,

107+

Math.min(descriptionCap, TELEGRAM_MAX_COMMAND_DESCRIPTION_LENGTH),

108+

);

105109

if (description !== command.description) {

106110

descriptionTrimmed = true;

107111

return Object.assign({}, command, { description });

@@ -278,6 +282,7 @@ function buildTelegramMenuResultCacheKey(params: {

278282

for (const command of params.allCommands) {

279283

updateTelegramCommandDigestField(digest, command.command);

280284

updateTelegramCommandDigestField(digest, command.description);

285+

updateTelegramCommandLocalizationDigest(digest, command.descriptionLocalizations);

281286

}

282287

return digest.digest("hex").slice(0, 16);

283288

}

@@ -291,6 +296,18 @@ function updateTelegramCommandDigestField(

291296

digest.update(value);

292297

}

293298299+

function updateTelegramCommandLocalizationDigest(

300+

digest: ReturnType<typeof createHash>,

301+

localizations: Record<string, string> | undefined,

302+

): void {

303+

const entries = Object.entries(localizations ?? {}).toSorted(([a], [b]) => a.localeCompare(b));

304+

updateTelegramCommandDigestField(digest, String(entries.length));

305+

for (const [locale, description] of entries) {

306+

updateTelegramCommandDigestField(digest, locale);

307+

updateTelegramCommandDigestField(digest, description);

308+

}

309+

}

310+294311

function rememberCappedTelegramMenuResult(

295312

key: string,

296313

result: ReturnType<typeof buildUncachedCappedTelegramMenuCommands>,

@@ -331,24 +348,74 @@ function writeCachedCommandHash(

331348

syncedCommandHashes.set(key, hash);

332349

}

333350334-

function buildLocalizedCommandVariants(

335-

commands: TelegramMenuCommand[],

336-

): Array<{ languageCode: string; commands: TelegramMenuCommand[] }> {

351+

function normalizeTelegramLanguageCode(languageCode: string): string | null {

352+

const normalized = languageCode.trim().toLowerCase();

353+

return /^[a-z]{2}$/.test(normalized) ? normalized : null;

354+

}

355+356+

function readLocalizedDescription(

357+

command: TelegramMenuCommand,

358+

languageCode: string,

359+

): string | undefined {

360+

for (const [rawLanguageCode, rawDescription] of Object.entries(

361+

command.descriptionLocalizations ?? {},

362+

)) {

363+

if (normalizeTelegramLanguageCode(rawLanguageCode) !== languageCode) {

364+

continue;

365+

}

366+

const description = normalizeOptionalString(rawDescription);

367+

if (description) {

368+

return description;

369+

}

370+

}

371+

return undefined;

372+

}

373+374+

function toTelegramBotCommands(commands: TelegramMenuCommand[]): Array<{

375+

command: string;

376+

description: string;

377+

}> {

378+

return commands.map((command) => ({

379+

command: command.command,

380+

description: command.description,

381+

}));

382+

}

383+384+

function buildLocalizedCommandVariants(commands: TelegramMenuCommand[]): {

385+

variants: Array<{ languageCode: string; commands: TelegramMenuCommand[] }>;

386+

unsupportedLanguageCodes: string[];

387+

} {

337388

const locales = new Set<string>();

389+

const unsupportedLanguageCodes = new Set<string>();

338390

for (const cmd of commands) {

339391

if (cmd.descriptionLocalizations) {

340392

for (const lang of Object.keys(cmd.descriptionLocalizations)) {

341-

locales.add(lang);

393+

const normalized = normalizeTelegramLanguageCode(lang);

394+

if (normalized) {

395+

locales.add(normalized);

396+

} else {

397+

unsupportedLanguageCodes.add(lang);

398+

}

342399

}

343400

}

344401

}

345-

return [...locales].toSorted().map((languageCode) => ({

346-

languageCode,

347-

commands: commands.map((cmd) => ({

402+

const variants = [...locales].toSorted().map((languageCode) => {

403+

const localizedCommands = commands.map((cmd) => ({

348404

command: cmd.command,

349-

description: cmd.descriptionLocalizations?.[languageCode] ?? cmd.description,

350-

})),

351-

}));

405+

description: readLocalizedDescription(cmd, languageCode) ?? cmd.description,

406+

}));

407+

return {

408+

languageCode,

409+

commands: fitTelegramCommandsWithinTextBudget(

410+

localizedCommands,

411+

TELEGRAM_TOTAL_COMMAND_TEXT_BUDGET,

412+

).commands,

413+

};

414+

});

415+

return {

416+

variants,

417+

unsupportedLanguageCodes: [...unsupportedLanguageCodes].toSorted(),

418+

};

352419

}

353420354421

function formatTelegramCommandScopeOperation(

@@ -398,13 +465,14 @@ async function setTelegramMenuCommandsForScopes(params: {

398465

runtime,

399466

shouldLog,

400467

fn: () => {

468+

const botCommands = toTelegramBotCommands(commands);

401469

const opts = {

402470

...scope.options,

403471

...(languageCode ? { language_code: languageCode as LanguageCode } : undefined),

404472

};

405473

return Object.keys(opts).length > 0

406-

? bot.api.setMyCommands(commands, opts)

407-

: bot.api.setMyCommands(commands);

474+

? bot.api.setMyCommands(botCommands, opts)

475+

: bot.api.setMyCommands(botCommands);

408476

},

409477

});

410478

}

@@ -446,6 +514,7 @@ export function syncTelegramMenuCommands(params: {

446514

}

447515448516

let retryCommands = commandsToRegister;

517+

let acceptedCommands: TelegramMenuCommand[] | null = null;

449518

const initialCommandCount = commandsToRegister.length;

450519

while (retryCommands.length > 0) {

451520

try {

@@ -463,6 +532,7 @@ export function syncTelegramMenuCommands(params: {

463532

}),

464533

);

465534

}

535+

acceptedCommands = retryCommands;

466536

break;

467537

} catch (err) {

468538

if (!isBotCommandsTooMuchError(err)) {

@@ -484,7 +554,18 @@ export function syncTelegramMenuCommands(params: {

484554

}

485555

}

486556487-

for (const variant of buildLocalizedCommandVariants(commandsToRegister)) {

557+

if (!acceptedCommands) {

558+

return;

559+

}

560+561+

const { variants, unsupportedLanguageCodes } = buildLocalizedCommandVariants(acceptedCommands);

562+

if (unsupportedLanguageCodes.length > 0) {

563+

runtime.log?.(

564+

`Telegram command menu ignored unsupported description localization codes: ${unsupportedLanguageCodes.join(", ")}.`,

565+

);

566+

}

567+568+

for (const variant of variants) {

488569

await setTelegramMenuCommandsForScopes({

489570

bot,

490571

runtime,