@link-assistant/hive-mind 1.69.11 → 1.69.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/limits-i18n.lib.mjs +145 -0
- package/src/limits.lib.mjs +46 -74
- package/src/locales/en.lino +52 -0
- package/src/locales/hi.lino +52 -0
- package/src/locales/ru.lino +53 -1
- package/src/locales/zh.lino +52 -0
- package/src/telegram-bot.mjs +9 -8
- package/src/telegram-safe-reply.lib.mjs +117 -14
- package/src/telegram-solve-queue-command.lib.mjs +18 -6
- package/src/telegram-solve-queue.helpers.lib.mjs +58 -6
- package/src/telegram-solve-queue.lib.mjs +65 -41
- package/src/version-info.lib.mjs +52 -15
package/src/locales/zh.lino
CHANGED
|
@@ -80,6 +80,10 @@ zh
|
|
|
80
80
|
limits.current_week_sonnet_only "本周(仅 Sonnet)"
|
|
81
81
|
limits.disabled_by_admin "`--show-limits` 已被机器人管理员禁用。"
|
|
82
82
|
limits.disk_space "磁盘空间"
|
|
83
|
+
limits.duration_day_short "天"
|
|
84
|
+
limits.duration_hour_short "小时"
|
|
85
|
+
limits.duration_minute_short "分钟"
|
|
86
|
+
limits.duration_second_short "秒"
|
|
83
87
|
limits.end "结束"
|
|
84
88
|
limits.five_hour_session "5 小时会话"
|
|
85
89
|
limits.five_min_load_avg "5 分钟平均负载"
|
|
@@ -92,9 +96,41 @@ zh
|
|
|
92
96
|
limits.passed "已过"
|
|
93
97
|
limits.plan "方案"
|
|
94
98
|
limits.ram "内存"
|
|
99
|
+
limits.reason_claude_5_hour_session "Claude 5 小时会话限额为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
100
|
+
limits.reason_claude_running "Claude 进程已在运行"
|
|
101
|
+
limits.reason_claude_weekly "Claude 每周限额为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
102
|
+
limits.reason_codex_5_hour_session "Codex 5 小时会话限额为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
103
|
+
limits.reason_codex_running "Codex 进程已在运行"
|
|
104
|
+
limits.reason_codex_weekly "Codex 每周限额为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
105
|
+
limits.reason_cpu_usage "CPU 使用率为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
106
|
+
limits.reason_disk_usage "磁盘使用率为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
107
|
+
limits.reason_gemini_running "Gemini CLI 进程已在运行"
|
|
108
|
+
limits.reason_github_api "GitHub API 使用率为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
109
|
+
limits.reason_min_interval "尚未达到命令之间的最小间隔"
|
|
110
|
+
limits.reason_qwen_running "Qwen Code 进程已在运行"
|
|
111
|
+
limits.reason_ram_usage "内存使用率为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
112
|
+
limits.reason_threshold_exceeded "{{metric}} 超过阈值"
|
|
113
|
+
limits.remaining "剩余 {{duration}}"
|
|
95
114
|
limits.requests "请求"
|
|
96
115
|
limits.resets_at "{{time}} 重置"
|
|
97
116
|
limits.resets_in "{{duration}} 后重置"
|
|
117
|
+
limits.resource_limit_exceeded "资源限额已超出"
|
|
118
|
+
limits.solve_queue_status "Solve 队列状态"
|
|
119
|
+
limits.queue_completed "已完成"
|
|
120
|
+
limits.queue_failed "失败"
|
|
121
|
+
limits.queue_and_more "还有 {{count}} 个"
|
|
122
|
+
limits.queue_pending "等待"
|
|
123
|
+
limits.queue_processing "处理中"
|
|
124
|
+
limits.queue_processes "{{count}} 个进程"
|
|
125
|
+
limits.queue_status_cancelled "已取消"
|
|
126
|
+
limits.queue_status_failed "失败"
|
|
127
|
+
limits.queue_status_queued "已排队"
|
|
128
|
+
limits.queue_status_started "已启动"
|
|
129
|
+
limits.queue_status_starting "正在启动"
|
|
130
|
+
limits.queue_status_waiting "等待中"
|
|
131
|
+
limits.queue_waiting_current_command "等待当前命令"
|
|
132
|
+
limits.queue_waiting_in_queue "在队列中等待"
|
|
133
|
+
limits.queues "队列"
|
|
98
134
|
limits.seven_day_all_models "7 天所有模型"
|
|
99
135
|
limits.seven_day_sonnet_only "7 天仅 Sonnet"
|
|
100
136
|
limits.session "会话"
|
|
@@ -108,9 +144,11 @@ zh
|
|
|
108
144
|
telegram.gathering_version "🔄 正在收集版本信息……"
|
|
109
145
|
telegram.usage_limits_title "📊 *使用限额*"
|
|
110
146
|
telegram.version_information_title "🤖 *版本信息*"
|
|
147
|
+
telegram.formatting_fallback_warning "⚠️ 检测到格式错误。正在显示纯文本备用内容。"
|
|
111
148
|
telegram.limits_only_in_groups "❌ /limits 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
112
149
|
telegram.version_only_in_groups "❌ /version 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
113
150
|
telegram.solve_only_in_groups "❌ {{commandDisplay}} 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
151
|
+
telegram.solve_queue_only_in_groups "❌ /solve_queue 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
114
152
|
telegram.hive_only_in_groups "❌ /hive 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
115
153
|
telegram.solve_disabled "❌ 此机器人实例已禁用 solve 命令。"
|
|
116
154
|
telegram.hive_disabled "❌ 此机器人实例已禁用 /hive 命令。"
|
|
@@ -215,6 +253,20 @@ zh
|
|
|
215
253
|
language.ru "俄语"
|
|
216
254
|
language.zh "中文"
|
|
217
255
|
language.hi "印地语"
|
|
256
|
+
version.ai_agents "AI 代理"
|
|
257
|
+
version.browsers "浏览器"
|
|
258
|
+
version.browser_automation "浏览器自动化"
|
|
259
|
+
version.connected "已连接"
|
|
260
|
+
version.development_tools "开发工具"
|
|
261
|
+
version.environment "环境"
|
|
262
|
+
version.architecture "架构"
|
|
263
|
+
version.kernel "内核"
|
|
264
|
+
version.not_connected "未连接"
|
|
265
|
+
version.os "操作系统"
|
|
266
|
+
version.platform "平台"
|
|
267
|
+
version.process_running_restart_needed "正在运行的进程:`{{processVersion}}`(需要重启)"
|
|
268
|
+
version.system "系统"
|
|
269
|
+
version.version "版本"
|
|
218
270
|
prompt.user.issue_to_solve "要解决的问题:{{issueUrl}}"
|
|
219
271
|
prompt.user.issue_linked_to_pr "要解决的问题:与 PR #{{prNumber}} 关联的问题"
|
|
220
272
|
prompt.user.prepared_branch "为你准备的分支:{{branchName}}"
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -40,7 +40,7 @@ const { applySolveToolAlias, getFirstParsedPositionalArg, getSolveCommandNameFro
|
|
|
40
40
|
const { executeStartScreen: executeStartScreenCommand, buildExecuteAndUpdateMessage } = await import('./telegram-command-execution.lib.mjs');
|
|
41
41
|
const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STOP_REASON } = await import('./telegram-start-stop-command.lib.mjs');
|
|
42
42
|
const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
|
|
43
|
-
const { safeReply } = await import('./telegram-safe-reply.lib.mjs');
|
|
43
|
+
const { installTelegramFormattingFallback, safeEditMessageText, safeReply } = await import('./telegram-safe-reply.lib.mjs');
|
|
44
44
|
const { registerTerminalWatchCommand, startAutoTerminalWatchForSession } = await import('./telegram-terminal-watch-command.lib.mjs');
|
|
45
45
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
46
46
|
const { trackSession, startSessionMonitoring, hasActiveSessionForUrlAsync } = await import('./session-monitor.lib.mjs');
|
|
@@ -315,6 +315,7 @@ const { Telegraf } = telegrafModule;
|
|
|
315
315
|
const bot = new Telegraf(BOT_TOKEN, {
|
|
316
316
|
handlerTimeout: Infinity, // Remove default 90s timeout; command handlers like /solve spawn long-running processes
|
|
317
317
|
});
|
|
318
|
+
installTelegramFormattingFallback(bot.telegram, { verbose: VERBOSE });
|
|
318
319
|
|
|
319
320
|
// Track bot startup time (Unix seconds to match Telegram's message.date format)
|
|
320
321
|
const BOT_START_TIME = Math.floor(Date.now() / 1000);
|
|
@@ -514,7 +515,7 @@ bot.command('help', async ctx => {
|
|
|
514
515
|
allowTopicHint: topicId ? `TELEGRAM_ALLOWED_TOPICS="(${chatId} ${topicId})"` : '',
|
|
515
516
|
});
|
|
516
517
|
|
|
517
|
-
await ctx
|
|
518
|
+
await safeReply(ctx, message, { fallbackLocale: helpLocale });
|
|
518
519
|
});
|
|
519
520
|
|
|
520
521
|
bot.command('limits', async ctx => {
|
|
@@ -564,10 +565,10 @@ bot.command('limits', async ctx => {
|
|
|
564
565
|
const claudeError = limits.claude.success ? null : limits.claude.error;
|
|
565
566
|
const codexError = limits.codex.success ? null : limits.codex.error;
|
|
566
567
|
const solveQueue = getSolveQueue({ verbose: VERBOSE });
|
|
567
|
-
const queueStatus = await solveQueue.formatStatus();
|
|
568
|
+
const queueStatus = await solveQueue.formatStatus({ locale: userLocale });
|
|
568
569
|
const codexSection = formatCodexLimitsSection(limits.codex.success ? limits.codex : null, codexError, { locale: userLocale });
|
|
569
570
|
const message = t('telegram.usage_limits_title', {}, { locale: userLocale }) + '\n\n' + formatUsageMessage(limits.claude.success ? limits.claude.usage : null, limits.disk.success ? limits.disk.diskSpace : null, limits.github.success ? limits.github.githubRateLimit : null, limits.cpu.success ? limits.cpu.cpuLoad : null, limits.memory.success ? limits.memory.memory : null, claudeError, [codexSection, queueStatus], { locale: userLocale });
|
|
570
|
-
await ctx.telegram
|
|
571
|
+
await safeEditMessageText(ctx.telegram, fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'Markdown', fallbackLocale: userLocale, verbose: VERBOSE });
|
|
571
572
|
});
|
|
572
573
|
bot.command('version', async ctx => {
|
|
573
574
|
VERBOSE && console.log('[VERBOSE] /version command received');
|
|
@@ -585,8 +586,8 @@ bot.command('version', async ctx => {
|
|
|
585
586
|
reply_to_message_id: ctx.message.message_id,
|
|
586
587
|
});
|
|
587
588
|
const result = await getVersionInfo(VERBOSE);
|
|
588
|
-
if (!result.success) return await ctx.telegram
|
|
589
|
-
await ctx.telegram
|
|
589
|
+
if (!result.success) return await safeEditMessageText(ctx.telegram, fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `❌ ${escapeMarkdownV2(result.error, { preserveCodeBlocks: true })}`, { parse_mode: 'MarkdownV2', fallbackLocale: versionLocale, verbose: VERBOSE });
|
|
590
|
+
await safeEditMessageText(ctx.telegram, fetchingMessage.chat.id, fetchingMessage.message_id, undefined, t('telegram.version_information_title', {}, { locale: versionLocale }) + '\n\n' + formatVersionMessage(result.versions, { locale: versionLocale }), { parse_mode: 'Markdown', fallbackLocale: versionLocale, verbose: VERBOSE });
|
|
590
591
|
});
|
|
591
592
|
|
|
592
593
|
const { registerLanguageCommand } = await import('./telegram-language-command.lib.mjs');
|
|
@@ -598,7 +599,7 @@ registerAcceptInvitesCommand(bot, sharedCommandOpts);
|
|
|
598
599
|
const { registerMergeCommand } = await import('./telegram-merge-command.lib.mjs');
|
|
599
600
|
registerMergeCommand(bot, sharedCommandOpts);
|
|
600
601
|
const { registerSolveQueueCommand } = await import('./telegram-solve-queue-command.lib.mjs');
|
|
601
|
-
const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, { ...sharedCommandOpts, getSolveQueue });
|
|
602
|
+
const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, { ...sharedCommandOpts, getSolveQueue, safeReply, resolveLocale: resolveLocaleFromTelegramCtx });
|
|
602
603
|
const { registerSubscribeCommands } = await import('./telegram-subscribers.lib.mjs'); // #1688
|
|
603
604
|
registerSubscribeCommands(bot, sharedCommandOpts);
|
|
604
605
|
const { registerTaskCommands } = await import('./telegram-task-command.lib.mjs');
|
|
@@ -848,7 +849,7 @@ async function handleSolveCommand(ctx) {
|
|
|
848
849
|
await safeReply(ctx, t('telegram.url_session_running', { url: escapeMarkdown(normalizedUrl), session: activeSession.sessionName }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
849
850
|
return;
|
|
850
851
|
}
|
|
851
|
-
const check = await solveQueue.canStartCommand({ tool: solveTool }); // Skip Claude limits for agent (#1159)
|
|
852
|
+
const check = await solveQueue.canStartCommand({ tool: solveTool, locale: solveLocale }); // Skip Claude limits for agent (#1159)
|
|
852
853
|
const queueStats = solveQueue.getStats();
|
|
853
854
|
// Handle rejection: threshold strategy is 'reject' — fail immediately (issue #1267)
|
|
854
855
|
if (check.rejected) {
|
|
@@ -1,19 +1,122 @@
|
|
|
1
|
-
|
|
1
|
+
import { normalizeLocale, t } from './i18n.lib.mjs';
|
|
2
|
+
|
|
3
|
+
const FORMATTING_FALLBACK_INSTALLED = Symbol.for('hiveMind.telegramFormattingFallbackInstalled');
|
|
4
|
+
const DEFAULT_FORMATTING_FALLBACK_WARNING = '⚠️ Formatting error detected. Showing plain text fallback.';
|
|
5
|
+
const FORMATTING_FALLBACK_WARNINGS = {
|
|
6
|
+
en: DEFAULT_FORMATTING_FALLBACK_WARNING,
|
|
7
|
+
ru: '⚠️ Обнаружена ошибка форматирования. Показываю обычный текст.',
|
|
8
|
+
zh: '⚠️ 检测到格式错误。正在显示纯文本备用内容。',
|
|
9
|
+
hi: '⚠️ फ़ॉर्मैटिंग त्रुटि मिली। सादा पाठ fallback दिखाया जा रहा है।',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function splitOptions(options = {}) {
|
|
13
|
+
const { fallbackLocale, locale, verbose, ...telegramOptions } = options || {};
|
|
14
|
+
return {
|
|
15
|
+
telegramOptions,
|
|
16
|
+
fallbackLocale: fallbackLocale || locale || null,
|
|
17
|
+
verbose: Boolean(verbose),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isTelegramFormattingError(error) {
|
|
22
|
+
const message = error?.description || error?.message || String(error || '');
|
|
23
|
+
return /can't parse entities/i.test(message) || /can't find end of/i.test(message) || /entity.*parse/i.test(message) || (/bad request/i.test(message) && /400|parse|entity/i.test(message));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function stripTelegramMarkdown(text) {
|
|
27
|
+
return String(text ?? '')
|
|
28
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ($2)')
|
|
29
|
+
.replace(/```([\s\S]*?)```/g, '$1')
|
|
30
|
+
.replace(/\\_/g, '_')
|
|
31
|
+
.replace(/\\\*/g, '*')
|
|
32
|
+
.replace(/\\`/g, '`')
|
|
33
|
+
.replace(/\\\[/g, '[')
|
|
34
|
+
.replace(/\*([^*\n]+)\*/g, '$1')
|
|
35
|
+
.replace(/_([^_\n]+)_/g, '$1')
|
|
36
|
+
.replace(/`([^`]+)`/g, '$1');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getFormattingFallbackWarning(locale) {
|
|
40
|
+
const key = 'telegram.formatting_fallback_warning';
|
|
41
|
+
const normalizedLocale = normalizeLocale(locale);
|
|
42
|
+
const warning = t(key, {}, locale ? { locale } : {});
|
|
43
|
+
if (warning !== key && (normalizedLocale === 'en' || warning !== DEFAULT_FORMATTING_FALLBACK_WARNING)) return warning;
|
|
44
|
+
return FORMATTING_FALLBACK_WARNINGS[normalizedLocale] || DEFAULT_FORMATTING_FALLBACK_WARNING;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildTelegramFormattingFallbackText(text, options = {}) {
|
|
48
|
+
const locale = options?.fallbackLocale || options?.locale || null;
|
|
49
|
+
return `${getFormattingFallbackWarning(locale)}\n\n${stripTelegramMarkdown(text)}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function logFormattingFailure(scope, error, text, verbose = false) {
|
|
53
|
+
const message = error?.description || error?.message || String(error || '');
|
|
54
|
+
console.error(`[telegram-bot] ${scope}: formatted Telegram message failed: ${message}`);
|
|
55
|
+
if (verbose) {
|
|
56
|
+
console.error(`[telegram-bot] ${scope}: Failing message (${Buffer.byteLength(String(text ?? ''), 'utf-8')} bytes): ${text}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Issue #1460/#1497/#1788: try Markdown first, fall back to localized plain text on parsing errors.
|
|
2
61
|
export async function safeReply(ctx, text, options = {}) {
|
|
62
|
+
const { telegramOptions, fallbackLocale, verbose } = splitOptions(options);
|
|
63
|
+
const firstOptions = { parse_mode: 'Markdown', ...telegramOptions };
|
|
3
64
|
try {
|
|
4
|
-
return await ctx.reply(text,
|
|
65
|
+
return await ctx.reply(text, firstOptions);
|
|
5
66
|
} catch (error) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
console.error(`[telegram-bot] safeReply: Failing message (${Buffer.byteLength(text, 'utf-8')} bytes): ${text}`);
|
|
11
|
-
const plainText = text
|
|
12
|
-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ($2)')
|
|
13
|
-
.replace(/\\_/g, '_')
|
|
14
|
-
.replace(/\\\*/g, '*')
|
|
15
|
-
.replace(/\*([^*]+)\*/g, '$1')
|
|
16
|
-
.replace(/`([^`]+)`/g, '$1');
|
|
17
|
-
return await ctx.reply(plainText, { ...options, parse_mode: undefined });
|
|
67
|
+
if (!isTelegramFormattingError(error)) throw error;
|
|
68
|
+
logFormattingFailure('safeReply', error, text, verbose);
|
|
69
|
+
const fallbackText = buildTelegramFormattingFallbackText(text, { fallbackLocale });
|
|
70
|
+
return await ctx.reply(fallbackText, { ...telegramOptions, parse_mode: undefined });
|
|
18
71
|
}
|
|
19
72
|
}
|
|
73
|
+
|
|
74
|
+
export async function safeEditMessageText(telegram, chatId, messageId, inlineMessageId, text, options = {}) {
|
|
75
|
+
const { telegramOptions, fallbackLocale, verbose } = splitOptions(options);
|
|
76
|
+
const firstOptions = { parse_mode: 'Markdown', ...telegramOptions };
|
|
77
|
+
try {
|
|
78
|
+
return await telegram.editMessageText(chatId, messageId, inlineMessageId, text, firstOptions);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (!isTelegramFormattingError(error)) throw error;
|
|
81
|
+
logFormattingFailure('safeEditMessageText', error, text, verbose);
|
|
82
|
+
const fallbackText = buildTelegramFormattingFallbackText(text, { fallbackLocale });
|
|
83
|
+
return await telegram.editMessageText(chatId, messageId, inlineMessageId, fallbackText, { ...telegramOptions, parse_mode: undefined });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function wrapTelegramMethod(telegram, methodName, textIndex, optionsIndex, defaults = {}) {
|
|
88
|
+
const original = telegram?.[methodName];
|
|
89
|
+
if (typeof original !== 'function') return;
|
|
90
|
+
|
|
91
|
+
telegram[methodName] = async function wrappedTelegramMessageMethod(...args) {
|
|
92
|
+
const text = args[textIndex];
|
|
93
|
+
const originalOptions = args[optionsIndex] || {};
|
|
94
|
+
const { telegramOptions, fallbackLocale, verbose } = splitOptions(originalOptions);
|
|
95
|
+
args[optionsIndex] = telegramOptions;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
return await original.apply(this, args);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (!isTelegramFormattingError(error) || typeof text !== 'string') throw error;
|
|
101
|
+
logFormattingFailure(methodName, error, text, verbose || defaults.verbose);
|
|
102
|
+
const retryArgs = [...args];
|
|
103
|
+
retryArgs[textIndex] = buildTelegramFormattingFallbackText(text, { fallbackLocale: fallbackLocale || defaults.fallbackLocale });
|
|
104
|
+
retryArgs[optionsIndex] = { ...telegramOptions, parse_mode: undefined };
|
|
105
|
+
return await original.apply(this, retryArgs);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function installTelegramFormattingFallback(telegram, options = {}) {
|
|
111
|
+
if (!telegram || telegram[FORMATTING_FALLBACK_INSTALLED]) return telegram;
|
|
112
|
+
|
|
113
|
+
const defaults = {
|
|
114
|
+
fallbackLocale: options.fallbackLocale || options.locale || null,
|
|
115
|
+
verbose: Boolean(options.verbose),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
wrapTelegramMethod(telegram, 'sendMessage', 1, 2, defaults);
|
|
119
|
+
wrapTelegramMethod(telegram, 'editMessageText', 3, 4, defaults);
|
|
120
|
+
telegram[FORMATTING_FALLBACK_INSTALLED] = true;
|
|
121
|
+
return telegram;
|
|
122
|
+
}
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
* @see https://github.com/link-assistant/hive-mind/issues/1232
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { t } from './i18n.lib.mjs';
|
|
17
|
+
|
|
18
|
+
const GROUP_ONLY_MESSAGE = '❌ The /solve_queue command only works in group chats. Please add this bot to a group and make it an admin.';
|
|
19
|
+
|
|
20
|
+
function commandText(key, params = {}, locale = null, fallback = key) {
|
|
21
|
+
const translated = t(key, params, locale ? { locale } : {});
|
|
22
|
+
return translated === key ? fallback : translated;
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* Registers the /solve_queue command handler with the bot
|
|
18
27
|
* @param {Object} bot - The Telegraf bot instance
|
|
@@ -29,7 +38,7 @@
|
|
|
29
38
|
* @returns {{ handleSolveQueueCommand: Function }} The command handler for use in text fallback
|
|
30
39
|
*/
|
|
31
40
|
export function registerSolveQueueCommand(bot, options) {
|
|
32
|
-
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, getSolveQueue } = options;
|
|
41
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, getSolveQueue, safeReply, resolveLocale } = options;
|
|
33
42
|
|
|
34
43
|
async function handleSolveQueueCommand(ctx) {
|
|
35
44
|
VERBOSE && console.log('[VERBOSE] /solve_queue command received');
|
|
@@ -40,6 +49,8 @@ export function registerSolveQueueCommand(bot, options) {
|
|
|
40
49
|
level: 'info',
|
|
41
50
|
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
42
51
|
});
|
|
52
|
+
const locale = resolveLocale ? resolveLocale(ctx) : null;
|
|
53
|
+
const replyWithFallback = (text, replyOptions = {}) => (safeReply ? safeReply(ctx, text, replyOptions) : ctx.reply(text, { parse_mode: 'Markdown', ...replyOptions }));
|
|
43
54
|
|
|
44
55
|
// Ignore messages sent before bot started
|
|
45
56
|
if (isOldMessage(ctx)) {
|
|
@@ -55,8 +66,9 @@ export function registerSolveQueueCommand(bot, options) {
|
|
|
55
66
|
|
|
56
67
|
if (!isGroupChat(ctx)) {
|
|
57
68
|
VERBOSE && console.log('[VERBOSE] /solve_queue ignored: not a group chat');
|
|
58
|
-
await
|
|
69
|
+
await replyWithFallback(commandText('telegram.solve_queue_only_in_groups', {}, locale, GROUP_ONLY_MESSAGE), {
|
|
59
70
|
reply_to_message_id: ctx.message.message_id,
|
|
71
|
+
fallbackLocale: locale,
|
|
60
72
|
});
|
|
61
73
|
return;
|
|
62
74
|
}
|
|
@@ -65,7 +77,7 @@ export function registerSolveQueueCommand(bot, options) {
|
|
|
65
77
|
if (!authorize(ctx)) {
|
|
66
78
|
VERBOSE && console.log('[VERBOSE] /solve_queue ignored: not authorized');
|
|
67
79
|
const errMsg = buildAuthErrorMessage ? buildAuthErrorMessage(ctx) : `❌ This chat (ID: ${ctx.chat.id}) is not authorized.`;
|
|
68
|
-
await
|
|
80
|
+
await replyWithFallback(errMsg, { reply_to_message_id: ctx.message.message_id, fallbackLocale: locale });
|
|
69
81
|
return;
|
|
70
82
|
}
|
|
71
83
|
|
|
@@ -77,11 +89,11 @@ export function registerSolveQueueCommand(bot, options) {
|
|
|
77
89
|
// Shows per-queue breakdown with first 5 items per queue and human-readable times
|
|
78
90
|
// Processing counts are actual running system processes (via pgrep)
|
|
79
91
|
// See: https://github.com/link-assistant/hive-mind/issues/1267
|
|
80
|
-
const message = await solveQueue.formatDetailedStatus();
|
|
92
|
+
const message = await solveQueue.formatDetailedStatus({ locale });
|
|
81
93
|
|
|
82
|
-
await
|
|
83
|
-
parse_mode: 'Markdown',
|
|
94
|
+
await replyWithFallback(message, {
|
|
84
95
|
reply_to_message_id: ctx.message.message_id,
|
|
96
|
+
fallbackLocale: locale,
|
|
85
97
|
});
|
|
86
98
|
}
|
|
87
99
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { exec } from 'node:child_process';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { lt } from './limits-i18n.lib.mjs';
|
|
5
6
|
|
|
6
7
|
const execAsync = promisify(exec);
|
|
7
8
|
|
|
@@ -111,8 +112,9 @@ export function formatThresholdPercent(ratio) {
|
|
|
111
112
|
* @returns {string} Human-readable duration
|
|
112
113
|
* @see https://github.com/link-assistant/hive-mind/issues/1267
|
|
113
114
|
*/
|
|
114
|
-
export function formatDuration(ms) {
|
|
115
|
+
export function formatDuration(ms, options = {}) {
|
|
115
116
|
if (ms < 0) ms = 0;
|
|
117
|
+
const locale = typeof options === 'string' ? options : options?.locale || null;
|
|
116
118
|
|
|
117
119
|
const totalSeconds = Math.floor(ms / 1000);
|
|
118
120
|
const days = Math.floor(totalSeconds / 86400);
|
|
@@ -120,11 +122,26 @@ export function formatDuration(ms) {
|
|
|
120
122
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
121
123
|
const seconds = totalSeconds % 60;
|
|
122
124
|
|
|
125
|
+
const labels =
|
|
126
|
+
locale && locale !== 'en'
|
|
127
|
+
? {
|
|
128
|
+
day: lt('duration_day_short', {}, { locale }),
|
|
129
|
+
hour: lt('duration_hour_short', {}, { locale }),
|
|
130
|
+
minute: lt('duration_minute_short', {}, { locale }),
|
|
131
|
+
second: lt('duration_second_short', {}, { locale }),
|
|
132
|
+
}
|
|
133
|
+
: {
|
|
134
|
+
day: 'd',
|
|
135
|
+
hour: 'h',
|
|
136
|
+
minute: 'm',
|
|
137
|
+
second: 's',
|
|
138
|
+
};
|
|
139
|
+
|
|
123
140
|
const parts = [];
|
|
124
|
-
if (days > 0) parts.push(`${days}
|
|
125
|
-
if (hours > 0) parts.push(`${hours}
|
|
126
|
-
if (minutes > 0) parts.push(`${minutes}
|
|
127
|
-
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}
|
|
141
|
+
if (days > 0) parts.push(`${days}${locale && locale !== 'en' ? ' ' : ''}${labels.day}`);
|
|
142
|
+
if (hours > 0) parts.push(`${hours}${locale && locale !== 'en' ? ' ' : ''}${labels.hour}`);
|
|
143
|
+
if (minutes > 0) parts.push(`${minutes}${locale && locale !== 'en' ? ' ' : ''}${labels.minute}`);
|
|
144
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}${locale && locale !== 'en' ? ' ' : ''}${labels.second}`);
|
|
128
145
|
|
|
129
146
|
return parts.join(' ');
|
|
130
147
|
}
|
|
@@ -136,9 +153,44 @@ export function formatDuration(ms) {
|
|
|
136
153
|
* @param {number} threshold - Threshold ratio (0.0 - 1.0)
|
|
137
154
|
* @returns {string} Human-readable reason
|
|
138
155
|
*/
|
|
139
|
-
export function formatWaitingReason(metric, currentValue, threshold) {
|
|
156
|
+
export function formatWaitingReason(metric, currentValue, threshold, options = {}) {
|
|
157
|
+
const locale = typeof options === 'string' ? options : options?.locale || null;
|
|
140
158
|
const thresholdPercent = formatThresholdPercent(threshold);
|
|
141
159
|
const currentPercent = Math.round(currentValue);
|
|
160
|
+
const params = { currentPercent, thresholdPercent, metric };
|
|
161
|
+
|
|
162
|
+
if (locale && locale !== 'en') {
|
|
163
|
+
switch (metric) {
|
|
164
|
+
case 'ram':
|
|
165
|
+
return lt('reason_ram_usage', params, { locale });
|
|
166
|
+
case 'cpu':
|
|
167
|
+
return lt('reason_cpu_usage', params, { locale });
|
|
168
|
+
case 'disk':
|
|
169
|
+
return lt('reason_disk_usage', params, { locale });
|
|
170
|
+
case 'claude_5_hour_session':
|
|
171
|
+
return lt('reason_claude_5_hour_session', params, { locale });
|
|
172
|
+
case 'claude_weekly':
|
|
173
|
+
return lt('reason_claude_weekly', params, { locale });
|
|
174
|
+
case 'codex_5_hour_session':
|
|
175
|
+
return lt('reason_codex_5_hour_session', params, { locale });
|
|
176
|
+
case 'codex_weekly':
|
|
177
|
+
return lt('reason_codex_weekly', params, { locale });
|
|
178
|
+
case 'github':
|
|
179
|
+
return lt('reason_github_api', params, { locale });
|
|
180
|
+
case 'min_interval':
|
|
181
|
+
return lt('reason_min_interval', params, { locale });
|
|
182
|
+
case 'claude_running':
|
|
183
|
+
return lt('reason_claude_running', params, { locale });
|
|
184
|
+
case 'codex_running':
|
|
185
|
+
return lt('reason_codex_running', params, { locale });
|
|
186
|
+
case 'qwen_running':
|
|
187
|
+
return lt('reason_qwen_running', params, { locale });
|
|
188
|
+
case 'gemini_running':
|
|
189
|
+
return lt('reason_gemini_running', params, { locale });
|
|
190
|
+
default:
|
|
191
|
+
return lt('reason_threshold_exceeded', params, { locale });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
142
194
|
|
|
143
195
|
switch (metric) {
|
|
144
196
|
case 'ram':
|