@link-assistant/hive-mind 1.69.4 ā 1.69.6
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 +12 -0
- package/package.json +3 -2
- package/src/auto-language.lib.mjs +94 -0
- package/src/limits-i18n.lib.mjs +71 -0
- package/src/limits.lib.mjs +78 -80
- package/src/locales/en.lino +124 -0
- package/src/locales/hi.lino +124 -0
- package/src/locales/ru.lino +124 -0
- package/src/locales/zh.lino +124 -0
- package/src/session-monitor.lib.mjs +4 -2
- package/src/solve.config.lib.mjs +21 -0
- package/src/solve.mjs +12 -10
- package/src/telegram-bot.mjs +69 -135
- package/src/telegram-command-execution.lib.mjs +5 -4
- package/src/telegram-show-limits.lib.mjs +39 -32
- package/src/telegram-solve-queue.lib.mjs +7 -3
- package/src/telegram-terminal-watch-command.lib.mjs +32 -16
- package/src/telegram-ui-messages.lib.mjs +112 -0
- package/src/work-session-formatting.lib.mjs +33 -14
package/src/solve.config.lib.mjs
CHANGED
|
@@ -572,8 +572,23 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
572
572
|
description: "Working language passed to the AI tool (Claude/Codex/etc). Used as the tool's preferred language for translations and prompts. Defaults to --language.",
|
|
573
573
|
choices: ['en', 'ru', 'zh', 'hi'],
|
|
574
574
|
},
|
|
575
|
+
'prompt-language': {
|
|
576
|
+
type: 'string',
|
|
577
|
+
description: 'Deprecated alias for --work-language.',
|
|
578
|
+
choices: ['en', 'ru', 'zh', 'hi'],
|
|
579
|
+
hidden: true,
|
|
580
|
+
},
|
|
581
|
+
'auto-language': {
|
|
582
|
+
type: 'boolean',
|
|
583
|
+
description: 'Experimental and disabled by default. Automatically detect the target issue or pull request language and set the AI work language to English or Russian when one language has more than 51% of all words. Explicit --work-language or --prompt-language takes precedence.',
|
|
584
|
+
default: false,
|
|
585
|
+
},
|
|
575
586
|
};
|
|
576
587
|
|
|
588
|
+
function hasRawOption(rawArgs, optionName) {
|
|
589
|
+
return rawArgs.some(arg => arg === optionName || arg.startsWith(`${optionName}=`));
|
|
590
|
+
}
|
|
591
|
+
|
|
577
592
|
// Function to create yargs configuration - avoids duplication
|
|
578
593
|
export const createYargsConfig = yargsInstance => {
|
|
579
594
|
let config = yargsInstance
|
|
@@ -749,6 +764,12 @@ export const parseArguments = async (yargs = getLinoYargsFactory(), hideBinFn =
|
|
|
749
764
|
if (argv.disableIssueAutoCreationOnError) {
|
|
750
765
|
argv.disableReportIssue = true;
|
|
751
766
|
}
|
|
767
|
+
const workLanguageExplicit = hasRawOption(rawArgs, '--work-language');
|
|
768
|
+
const promptLanguageExplicit = hasRawOption(rawArgs, '--prompt-language');
|
|
769
|
+
if (argv.promptLanguage && !workLanguageExplicit) {
|
|
770
|
+
argv.workLanguage = argv.promptLanguage;
|
|
771
|
+
}
|
|
772
|
+
argv._workLanguageExplicit = workLanguageExplicit || promptLanguageExplicit;
|
|
752
773
|
}
|
|
753
774
|
|
|
754
775
|
// --finalize normalization
|
package/src/solve.mjs
CHANGED
|
@@ -84,16 +84,6 @@ try {
|
|
|
84
84
|
}
|
|
85
85
|
global.verboseMode = argv.verbose;
|
|
86
86
|
|
|
87
|
-
// Initialize i18n based on --language / --ui-language / --work-language
|
|
88
|
-
// (or detected system locale). --language sets both tracks by default;
|
|
89
|
-
// --ui-language and --work-language can override each track independently.
|
|
90
|
-
const { initI18n } = await import('./i18n.lib.mjs');
|
|
91
|
-
await initI18n({
|
|
92
|
-
language: argv.language,
|
|
93
|
-
uiLanguage: argv.uiLanguage,
|
|
94
|
-
workLanguage: argv.workLanguage,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
87
|
setupVerboseLogInterceptor(); // Issue #1466: capture [VERBOSE] output in log files
|
|
98
88
|
setupStdioLogInterceptor(); // Issue #1549: capture ALL terminal output in log file
|
|
99
89
|
|
|
@@ -180,6 +170,18 @@ if (isIssueUrl) {
|
|
|
180
170
|
}
|
|
181
171
|
cleanupContext.owner = owner;
|
|
182
172
|
cleanupContext.repo = repo;
|
|
173
|
+
if (argv.autoLanguage) {
|
|
174
|
+
const { applyAutoLanguageToArgv } = await import('./auto-language.lib.mjs');
|
|
175
|
+
await applyAutoLanguageToArgv({ argv, githubLib, owner, repo, number: urlNumber, isIssueUrl, isPrUrl, log });
|
|
176
|
+
}
|
|
177
|
+
// Initialize i18n based on --language / --ui-language / --work-language
|
|
178
|
+
// (or detected system locale). --auto-language may set only the work track.
|
|
179
|
+
const { initI18n } = await import('./i18n.lib.mjs');
|
|
180
|
+
await initI18n({
|
|
181
|
+
language: argv.language,
|
|
182
|
+
uiLanguage: argv.uiLanguage,
|
|
183
|
+
workLanguage: argv.workLanguage,
|
|
184
|
+
});
|
|
183
185
|
// Setup unhandled error handlers to ensure log path is always shown
|
|
184
186
|
const errorHandlerOptions = {
|
|
185
187
|
log,
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -45,6 +45,7 @@ const { registerTerminalWatchCommand, startAutoTerminalWatchForSession } = await
|
|
|
45
45
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
46
46
|
const { trackSession, startSessionMonitoring, hasActiveSessionForUrlAsync } = await import('./session-monitor.lib.mjs');
|
|
47
47
|
const { formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage } = await import('./work-session-formatting.lib.mjs');
|
|
48
|
+
const { buildTelegramHelpMessage, buildTelegramInfoBlock, buildSolveQueuedMessage } = await import('./telegram-ui-messages.lib.mjs');
|
|
48
49
|
|
|
49
50
|
const config = yargs(hideBin(process.argv))
|
|
50
51
|
.usage('Usage: hive-telegram-bot [options]')
|
|
@@ -442,12 +443,12 @@ async function getCommandUrlArg(args, createYargsConfig, positionalNames) {
|
|
|
442
443
|
}
|
|
443
444
|
|
|
444
445
|
async function validateGitHubUrl(args, options = {}) {
|
|
445
|
-
const { allowedTypes = ['issue', 'pull'], commandName = 'solve', createYargsConfig = null, positionalNames = [] } = options;
|
|
446
|
+
const { allowedTypes = ['issue', 'pull'], commandName = 'solve', createYargsConfig = null, positionalNames = [], locale = null } = options;
|
|
446
447
|
const rawUrl = await getCommandUrlArg(args, createYargsConfig, positionalNames);
|
|
447
|
-
if (!rawUrl) return { valid: false, error:
|
|
448
|
+
if (!rawUrl) return { valid: false, error: t('telegram.missing_github_url', { commandName }, { locale }) };
|
|
448
449
|
// Issue #1102: Clean non-printable chars (Zero-Width Space, BOM, etc.) from URLs
|
|
449
450
|
const url = cleanNonPrintableChars(rawUrl);
|
|
450
|
-
if (!url.includes('github.com')) return { valid: false, error: '
|
|
451
|
+
if (!url.includes('github.com')) return { valid: false, error: t('telegram.first_arg_must_be_github_url', {}, { locale }) };
|
|
451
452
|
const parsed = parseGitHubUrl(url);
|
|
452
453
|
if (!parsed.valid) return { valid: false, error: parsed.error || 'Invalid GitHub URL', suggestion: parsed.suggestion };
|
|
453
454
|
if (!allowedTypes.includes(parsed.type)) {
|
|
@@ -456,10 +457,10 @@ async function validateGitHubUrl(args, options = {}) {
|
|
|
456
457
|
const escapedUrl = escapeMarkdown(url),
|
|
457
458
|
escapedBaseUrl = escapeMarkdown(baseUrl); // Issue #1102: escape for Markdown
|
|
458
459
|
let error;
|
|
459
|
-
if (parsed.type === 'issues_list') error =
|
|
460
|
-
else if (parsed.type === 'pulls_list') error =
|
|
461
|
-
else if (parsed.type === 'repo') error =
|
|
462
|
-
else error =
|
|
460
|
+
if (parsed.type === 'issues_list') error = t('telegram.url_issues_list_error', { url: escapedUrl, example: `${escapedBaseUrl}/issues/1` }, { locale });
|
|
461
|
+
else if (parsed.type === 'pulls_list') error = t('telegram.url_pulls_list_error', { url: escapedUrl, example: `${escapedBaseUrl}/pull/1` }, { locale });
|
|
462
|
+
else if (parsed.type === 'repo') error = t('telegram.url_repo_error', { allowedTypes: allowedTypesStr, url: escapedUrl, example: `${escapedBaseUrl}/issues/1` }, { locale });
|
|
463
|
+
else error = t('telegram.url_must_be_type', { allowedTypes: allowedTypesStr, type: parsed.type.replace('_', ' ') }, { locale });
|
|
463
464
|
return { valid: false, error };
|
|
464
465
|
}
|
|
465
466
|
return { valid: true, parsed, normalizedUrl: url };
|
|
@@ -486,104 +487,32 @@ bot.command('help', async ctx => {
|
|
|
486
487
|
const chatType = ctx.chat.type;
|
|
487
488
|
const chatTitle = ctx.chat.title || 'Private Chat';
|
|
488
489
|
const topicId = ctx.message?.message_thread_id; // Forum topic ID (issue #1100)
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
message += 'Or reply to a message with a GitHub link: `/solve`\n';
|
|
516
|
-
if (solveOverrides.length > 0) {
|
|
517
|
-
message += `š Locked options: \`${solveOverrides.join(' ')}\`\n`;
|
|
518
|
-
}
|
|
519
|
-
message += '\n';
|
|
520
|
-
} else {
|
|
521
|
-
message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */qwen*) - ā Disabled\n\n';
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (taskEnabled) {
|
|
525
|
-
message += '*/task* - Create a GitHub issue from a repository link and issue text\n';
|
|
526
|
-
message += 'Usage: `/task <github-repository-url>` followed by issue text, or reply with `/task`\n';
|
|
527
|
-
message += 'Example: `/task https://github.com/owner/repo` then the issue text on following lines\n';
|
|
528
|
-
message += '*/split* - Split a GitHub issue into smaller issues\n';
|
|
529
|
-
message += 'Usage: `/split <github-issue-url> [options]` or `/task --split <github-issue-url>`\n';
|
|
530
|
-
message += 'Example: `/split https://github.com/owner/repo/issues/123 --split-count 2`\n\n';
|
|
531
|
-
} else {
|
|
532
|
-
message += '*/task* / */split* - ā Disabled\n\n';
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (hiveEnabled) {
|
|
536
|
-
message += '*/hive* - Run hive command\n';
|
|
537
|
-
message += 'Usage: `/hive <github-url> [options]`\n';
|
|
538
|
-
message += 'Example: `/hive https://github.com/owner/repo`\n';
|
|
539
|
-
if (hiveOverrides.length > 0) {
|
|
540
|
-
message += `š Locked options: \`${hiveOverrides.join(' ')}\`\n`;
|
|
541
|
-
}
|
|
542
|
-
message += '\n';
|
|
543
|
-
} else {
|
|
544
|
-
message += '*/hive* - ā Disabled\n\n';
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
message += '`/solve_queue` - Show solve queue status\n';
|
|
548
|
-
message += '*/limits* - Show usage limits\n';
|
|
549
|
-
message += '*/version* - Show bot and runtime versions\n';
|
|
550
|
-
message += '*/language* `[en|ru|zh|hi]` - Set or show your preferred reply language (in-memory only, per-user)\n';
|
|
551
|
-
message += '`/accept_invites` - Accept all pending GitHub invitations\n';
|
|
552
|
-
message += '*/merge* - Merge queue (experimental)\n';
|
|
553
|
-
message += 'Usage: `/merge <github-repo-url>`\n';
|
|
554
|
-
message += "Merges all PRs with 'ready' label sequentially.\n";
|
|
555
|
-
message += '*/subscribe* / */unsubscribe* - š Get private DM forward of /solve completion (experimental, #1688)\n';
|
|
556
|
-
message += '*/help* - Show this help message\n';
|
|
557
|
-
message += '*/stop* / */start* - Stop or resume accepting new tasks (owner only)\n';
|
|
558
|
-
message += '*/stop* `<uuid>` - Send CTRL+C to an isolated solve/hive session (owner only). Also works as a reply to a message containing the UUID.\n';
|
|
559
|
-
message += '*/log* - Fetch isolation session log (owner only). Usage: `/log <uuid>` or reply with `/log`\n';
|
|
560
|
-
message += '*/terminal\\_watch* - Live-update an isolation session log (owner only). Usage: `/terminal_watch <uuid>` or reply with `/terminal_watch`\n\n';
|
|
561
|
-
message += 'š *Session Notifications:* Completion notifications are automatic; use /subscribe for private DM forwards.\n';
|
|
562
|
-
if (ISOLATION_BACKEND) message += `š *Isolation Mode:* \`${ISOLATION_BACKEND}\` (experimental)\n`;
|
|
563
|
-
message += '\n';
|
|
564
|
-
message += 'ā ļø *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /subscribe and /unsubscribe work in private and group chats.\n\n';
|
|
565
|
-
message += 'š§ *Common Options:*\n';
|
|
566
|
-
message += `⢠\`--model <model>\` or \`-m\` - ${buildModelOptionDescription()}\n`;
|
|
567
|
-
message += '⢠`--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)\n';
|
|
568
|
-
message += '⢠`--think <level>` - Thinking level (off/low/medium/high/xhigh/max) | `--thinking-budget <num>` - Token budget (0-63999)\n';
|
|
569
|
-
message += '⢠`--verbose` or `-v` - Verbose output | `--attach-logs` - Attach logs to PR\n';
|
|
570
|
-
if (SHOW_LIMITS_ENABLED) message += '⢠`--show-limits` - Experimental: embed Claude/Codex usage at start, end and delta (#594)\n';
|
|
571
|
-
message += '\nš” *Tip:* Many more options available. See full documentation for complete list.\n';
|
|
572
|
-
|
|
573
|
-
if (allowedChats || allowedTopics) {
|
|
574
|
-
const authorized = isTopicAuthorized(ctx);
|
|
575
|
-
message += `\nš *Restricted Mode:* Authorized: ${authorized ? 'ā
Yes' : 'ā No'}`;
|
|
576
|
-
if (!authorized && topicId) message += `\nš” To allow this topic: \`TELEGRAM_ALLOWED_TOPICS="(${chatId} ${topicId})"\``;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
message += '\n\nš§ *Troubleshooting:*\n';
|
|
580
|
-
message += 'If bot is not receiving messages:\n';
|
|
581
|
-
message += '1. Check privacy mode in @BotFather\n';
|
|
582
|
-
message += ' ⢠Send `/setprivacy` to @BotFather\n';
|
|
583
|
-
message += ' ⢠Choose "Disable" for your bot\n';
|
|
584
|
-
message += ' ⢠Remove bot from group and re-add\n';
|
|
585
|
-
message += '2. Or make bot an admin in the group\n';
|
|
586
|
-
message += '3. Restart bot with `--verbose` flag for diagnostics';
|
|
490
|
+
const helpLocale = resolveLocaleFromTelegramCtx(ctx);
|
|
491
|
+
const stopped = isChatStopped(chatId);
|
|
492
|
+
const stopInfo = stopped ? getChatStopInfo(chatId) : null;
|
|
493
|
+
const restrictedMode = Boolean(allowedChats || allowedTopics);
|
|
494
|
+
const authorized = restrictedMode ? isTopicAuthorized(ctx) : null;
|
|
495
|
+
const message = buildTelegramHelpMessage({
|
|
496
|
+
locale: helpLocale,
|
|
497
|
+
chatId,
|
|
498
|
+
chatType,
|
|
499
|
+
chatTitle,
|
|
500
|
+
topicId,
|
|
501
|
+
isStopped: stopped,
|
|
502
|
+
stopInfo,
|
|
503
|
+
stopReason: stopInfo?.reason || DEFAULT_STOP_REASON,
|
|
504
|
+
solveEnabled,
|
|
505
|
+
taskEnabled,
|
|
506
|
+
hiveEnabled,
|
|
507
|
+
solveOverrides,
|
|
508
|
+
hiveOverrides,
|
|
509
|
+
showLimitsEnabled: SHOW_LIMITS_ENABLED,
|
|
510
|
+
isolationBackend: ISOLATION_BACKEND,
|
|
511
|
+
modelDescription: buildModelOptionDescription(),
|
|
512
|
+
restrictedMode,
|
|
513
|
+
authorized,
|
|
514
|
+
allowTopicHint: topicId ? `TELEGRAM_ALLOWED_TOPICS="(${chatId} ${topicId})"` : '',
|
|
515
|
+
});
|
|
587
516
|
|
|
588
517
|
await ctx.reply(message, { parse_mode: 'Markdown' });
|
|
589
518
|
});
|
|
@@ -636,8 +565,8 @@ bot.command('limits', async ctx => {
|
|
|
636
565
|
const codexError = limits.codex.success ? null : limits.codex.error;
|
|
637
566
|
const solveQueue = getSolveQueue({ verbose: VERBOSE });
|
|
638
567
|
const queueStatus = await solveQueue.formatStatus();
|
|
639
|
-
const codexSection = formatCodexLimitsSection(limits.codex.success ? limits.codex : null, codexError);
|
|
640
|
-
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]);
|
|
568
|
+
const codexSection = formatCodexLimitsSection(limits.codex.success ? limits.codex : null, codexError, { locale: userLocale });
|
|
569
|
+
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 });
|
|
641
570
|
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'Markdown' });
|
|
642
571
|
});
|
|
643
572
|
bot.command('version', async ctx => {
|
|
@@ -754,7 +683,7 @@ async function handleSolveCommand(ctx) {
|
|
|
754
683
|
let userArgs = parseCommandArgs(ctx.message.text);
|
|
755
684
|
|
|
756
685
|
// Issue #594: strip --show-limits from userArgs (hive-telegram-bot virtual option).
|
|
757
|
-
const solveSL = await handleShowLimitsFlag({ ctx, safeReply, args: userArgs, enabled: SHOW_LIMITS_ENABLED });
|
|
686
|
+
const solveSL = await handleShowLimitsFlag({ ctx, safeReply, args: userArgs, enabled: SHOW_LIMITS_ENABLED, locale: solveLocale });
|
|
758
687
|
if (solveSL.handled) return;
|
|
759
688
|
const solveShowLimits = solveSL.showLimits;
|
|
760
689
|
userArgs = solveSL.args;
|
|
@@ -812,13 +741,13 @@ async function handleSolveCommand(ctx) {
|
|
|
812
741
|
return;
|
|
813
742
|
}
|
|
814
743
|
|
|
815
|
-
const validation = await validateGitHubUrl(userArgs, { createYargsConfig: createSolveYargsConfig, positionalNames: ['issue-url'] });
|
|
744
|
+
const validation = await validateGitHubUrl(userArgs, { createYargsConfig: createSolveYargsConfig, positionalNames: ['issue-url'], locale: solveLocale });
|
|
816
745
|
if (!validation.valid) {
|
|
817
746
|
let errorMsg = `ā ${validation.error}`;
|
|
818
747
|
if (validation.suggestion) {
|
|
819
|
-
errorMsg += `\n\n
|
|
748
|
+
errorMsg += `\n\n${t('telegram.did_you_mean', { suggestion: validation.suggestion }, { locale: solveLocale })}`;
|
|
820
749
|
}
|
|
821
|
-
errorMsg +=
|
|
750
|
+
errorMsg += `\n\n${t('telegram.solve_invalid_url_help', {}, { locale: solveLocale })}`;
|
|
822
751
|
await safeReply(ctx, errorMsg, { reply_to_message_id: ctx.message.message_id });
|
|
823
752
|
return;
|
|
824
753
|
}
|
|
@@ -896,10 +825,14 @@ async function handleSolveCommand(ctx) {
|
|
|
896
825
|
const requester = buildUserMention({ user: ctx.from, parseMode: 'Markdown' });
|
|
897
826
|
// #1228: only user options; #1460: escape; #1688: 'Issue:' / 'Pull request:' label so completion can append PR link.
|
|
898
827
|
const userOptionsRaw = userArgs.slice(1).join(' ');
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
828
|
+
let infoBlock = buildTelegramInfoBlock({
|
|
829
|
+
locale: solveLocale,
|
|
830
|
+
requester,
|
|
831
|
+
urlKind: validation.parsed?.type === 'pull' ? 'pullRequest' : 'issue',
|
|
832
|
+
url: escapeMarkdown(normalizedUrl),
|
|
833
|
+
optionsRaw: userOptionsRaw ? escapeMarkdown(userOptionsRaw) : '',
|
|
834
|
+
lockedOptions: solveOverrides.length > 0 ? escapeMarkdown(solveOverrides.join(' ')) : '',
|
|
835
|
+
});
|
|
903
836
|
const solveQueue = getSolveQueue({ verbose: VERBOSE });
|
|
904
837
|
|
|
905
838
|
// Check for duplicate URL in queue (issue #1080)
|
|
@@ -932,15 +865,14 @@ async function handleSolveCommand(ctx) {
|
|
|
932
865
|
|
|
933
866
|
// Issue #594: append "Limits at start" to infoBlock; thread snapshot via sessionInfo.
|
|
934
867
|
let solveLimitsAtStart = null;
|
|
935
|
-
if (solveShowLimits) ({ infoBlock, limitsAtStart: solveLimitsAtStart } = await captureStartSnapshotAndAppend({ infoBlock, tool: solveTool, verbose: VERBOSE, limitsLib, commandLabel: '/solve' }));
|
|
868
|
+
if (solveShowLimits) ({ infoBlock, limitsAtStart: solveLimitsAtStart } = await captureStartSnapshotAndAppend({ infoBlock, tool: solveTool, verbose: VERBOSE, limitsLib, commandLabel: '/solve', locale: solveLocale }));
|
|
936
869
|
|
|
937
870
|
if (check.canStart && toolQueuedCount === 0) {
|
|
938
|
-
const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
|
|
939
|
-
await executeAndUpdateMessage(ctx, startingMessage, 'solve', argsWithLocale, infoBlock, effectiveSolveIsolation, solveTool, solveUrlContext, { showLimits: solveShowLimits, limitsAtStart: solveLimitsAtStart });
|
|
871
|
+
const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock, locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
872
|
+
await executeAndUpdateMessage(ctx, startingMessage, 'solve', argsWithLocale, infoBlock, effectiveSolveIsolation, solveTool, solveUrlContext, { showLimits: solveShowLimits, limitsAtStart: solveLimitsAtStart, locale: solveLocale });
|
|
940
873
|
} else {
|
|
941
|
-
const queueItem = solveQueue.enqueue({ url: normalizedUrl, args: argsWithLocale, ctx, requester, infoBlock, tool: solveTool, perCommandIsolation: effectiveSolveIsolation, urlContext: solveUrlContext, showLimits: solveShowLimits, limitsAtStart: solveLimitsAtStart });
|
|
942
|
-
|
|
943
|
-
if (check.reason) queueMessage += `\n\nā³ Waiting: ${escapeMarkdown(check.reason)}`;
|
|
874
|
+
const queueItem = solveQueue.enqueue({ url: normalizedUrl, args: argsWithLocale, ctx, requester, infoBlock, tool: solveTool, perCommandIsolation: effectiveSolveIsolation, urlContext: solveUrlContext, showLimits: solveShowLimits, limitsAtStart: solveLimitsAtStart, locale: solveLocale });
|
|
875
|
+
const queueMessage = buildSolveQueuedMessage({ locale: solveLocale, tool: solveTool, position: toolQueuedCount + 1, infoBlock, reason: check.reason ? escapeMarkdown(check.reason) : '' }); // tool-specific position (#1551)
|
|
944
876
|
const queuedMessage = await safeReply(ctx, queueMessage, { reply_to_message_id: ctx.message.message_id });
|
|
945
877
|
queueItem.messageInfo = { chatId: queuedMessage.chat.id, messageId: queuedMessage.message_id };
|
|
946
878
|
if (!solveQueue.executeCallback) {
|
|
@@ -1028,17 +960,17 @@ async function handleHiveCommand(ctx) {
|
|
|
1028
960
|
let userArgs = parseCommandArgs(ctx.message.text);
|
|
1029
961
|
|
|
1030
962
|
// Issue #594: see /solve handler.
|
|
1031
|
-
const hiveSL = await handleShowLimitsFlag({ ctx, safeReply, args: userArgs, enabled: SHOW_LIMITS_ENABLED });
|
|
963
|
+
const hiveSL = await handleShowLimitsFlag({ ctx, safeReply, args: userArgs, enabled: SHOW_LIMITS_ENABLED, locale: hiveLocale });
|
|
1032
964
|
if (hiveSL.handled) return;
|
|
1033
965
|
const hiveShowLimits = hiveSL.showLimits;
|
|
1034
966
|
userArgs = hiveSL.args;
|
|
1035
967
|
|
|
1036
968
|
// Issue #1102: Allow issues_list/pulls_list URLs and normalize to repo URLs
|
|
1037
|
-
const validation = await validateGitHubUrl(userArgs, { allowedTypes: ['repo', 'organization', 'user', 'issues_list', 'pulls_list'], commandName: 'hive', createYargsConfig: createHiveYargsConfig, positionalNames: ['github-url'] });
|
|
969
|
+
const validation = await validateGitHubUrl(userArgs, { allowedTypes: ['repo', 'organization', 'user', 'issues_list', 'pulls_list'], commandName: 'hive', createYargsConfig: createHiveYargsConfig, positionalNames: ['github-url'], locale: hiveLocale });
|
|
1038
970
|
if (!validation.valid) {
|
|
1039
971
|
let errorMsg = `ā ${validation.error}`;
|
|
1040
|
-
if (validation.suggestion) errorMsg += `\n\n
|
|
1041
|
-
errorMsg +=
|
|
972
|
+
if (validation.suggestion) errorMsg += `\n\n${t('telegram.did_you_mean', { suggestion: escapeMarkdown(validation.suggestion) }, { locale: hiveLocale })}`;
|
|
973
|
+
errorMsg += `\n\n${t('telegram.hive_invalid_url_help', {}, { locale: hiveLocale })}`;
|
|
1042
974
|
await safeReply(ctx, errorMsg, { reply_to_message_id: ctx.message.message_id });
|
|
1043
975
|
return;
|
|
1044
976
|
}
|
|
@@ -1090,31 +1022,33 @@ async function handleHiveCommand(ctx) {
|
|
|
1090
1022
|
try {
|
|
1091
1023
|
await parseArgsWithYargs(args, yargs, createHiveYargsConfig);
|
|
1092
1024
|
} catch (error) {
|
|
1093
|
-
await safeReply(ctx,
|
|
1025
|
+
await safeReply(ctx, t('telegram.invalid_options', { message: escapeMarkdown(error.message || String(error)) }, { locale: hiveLocale }), {
|
|
1094
1026
|
reply_to_message_id: ctx.message.message_id,
|
|
1095
1027
|
});
|
|
1096
1028
|
return;
|
|
1097
1029
|
}
|
|
1098
1030
|
|
|
1099
1031
|
const requester = buildUserMention({ user: ctx.from, parseMode: 'Markdown' });
|
|
1100
|
-
const escapedUrl = escapeMarkdown(args[0]);
|
|
1101
1032
|
// Issue #1228: Show only user-provided options (exclude locked overrides to avoid duplication)
|
|
1102
1033
|
// Issue #1460: Escape options text to prevent Markdown parsing errors
|
|
1103
1034
|
const userOptionsRaw = normalizedArgs.slice(1).join(' ');
|
|
1104
|
-
let infoBlock =
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1035
|
+
let infoBlock = buildTelegramInfoBlock({
|
|
1036
|
+
locale: hiveLocale,
|
|
1037
|
+
requester,
|
|
1038
|
+
urlKind: 'url',
|
|
1039
|
+
url: escapeMarkdown(args[0]),
|
|
1040
|
+
optionsRaw: userOptionsRaw ? escapeMarkdown(userOptionsRaw) : '',
|
|
1041
|
+
lockedOptions: hiveOverrides.length > 0 ? escapeMarkdown(hiveOverrides.join(' ')) : '',
|
|
1042
|
+
});
|
|
1109
1043
|
|
|
1110
1044
|
// Issue #594: see /solve handler.
|
|
1111
1045
|
let hiveLimitsAtStart = null;
|
|
1112
|
-
if (hiveShowLimits) ({ infoBlock, limitsAtStart: hiveLimitsAtStart } = await captureStartSnapshotAndAppend({ infoBlock, tool: hiveTool, verbose: VERBOSE, limitsLib, commandLabel: '/hive' }));
|
|
1046
|
+
if (hiveShowLimits) ({ infoBlock, limitsAtStart: hiveLimitsAtStart } = await captureStartSnapshotAndAppend({ infoBlock, tool: hiveTool, verbose: VERBOSE, limitsLib, commandLabel: '/hive', locale: hiveLocale }));
|
|
1113
1047
|
|
|
1114
|
-
const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
|
|
1048
|
+
const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock, locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
1115
1049
|
// Issue #378: propagate user's effective Telegram locale to the spawned hive session.
|
|
1116
1050
|
const hiveArgsWithLocale = injectLanguageIfMissing(args, hiveLocale);
|
|
1117
|
-
await executeAndUpdateMessage(ctx, startingMessage, 'hive', hiveArgsWithLocale, infoBlock, effectiveHiveIsolation, hiveTool, null, { showLimits: hiveShowLimits, limitsAtStart: hiveLimitsAtStart });
|
|
1051
|
+
await executeAndUpdateMessage(ctx, startingMessage, 'hive', hiveArgsWithLocale, infoBlock, effectiveHiveIsolation, hiveTool, null, { showLimits: hiveShowLimits, limitsAtStart: hiveLimitsAtStart, locale: hiveLocale });
|
|
1118
1052
|
}
|
|
1119
1053
|
|
|
1120
1054
|
bot.command(/^hive$/i, handleHiveCommand);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
3
|
import { exec as execCallback } from 'child_process';
|
|
4
|
+
import { t } from './i18n.lib.mjs';
|
|
4
5
|
|
|
5
6
|
const exec = promisify(execCallback);
|
|
6
7
|
|
|
@@ -98,7 +99,7 @@ function executeWithCommand(startScreenCmd, command, args, verbose = false) {
|
|
|
98
99
|
*/
|
|
99
100
|
export function buildExecuteAndUpdateMessage(deps) {
|
|
100
101
|
const { resolveIsolation, ISOLATION_BACKEND, isolationRunner, VERBOSE, executeStartScreen, trackSession, AUTO_WATCH_MESSAGE, startAutoTerminalWatchForSession, bot, formatExecutingWorkSessionMessage } = deps;
|
|
101
|
-
return async function executeAndUpdateMessage(ctx, startingMessage, commandName, args, infoBlock, perCommandIsolation = null, tool = 'claude', urlContext = null, { showLimits = false, limitsAtStart = null } = {}) {
|
|
102
|
+
return async function executeAndUpdateMessage(ctx, startingMessage, commandName, args, infoBlock, perCommandIsolation = null, tool = 'claude', urlContext = null, { showLimits = false, limitsAtStart = null, locale = null } = {}) {
|
|
102
103
|
const { chat, message_id: msgId } = startingMessage;
|
|
103
104
|
const safeEdit = async text => {
|
|
104
105
|
try {
|
|
@@ -108,7 +109,7 @@ export function buildExecuteAndUpdateMessage(deps) {
|
|
|
108
109
|
}
|
|
109
110
|
};
|
|
110
111
|
const requesterUserId = ctx.from?.id ?? null; // Issue #1688: suppress duplicate /subscribe DM
|
|
111
|
-
const baseSessionInfo = { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, tool, infoBlock, urlContext, requesterUserId, showLimits, limitsAtStart }; // #594: showLimits/limitsAtStart
|
|
112
|
+
const baseSessionInfo = { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, tool, infoBlock, urlContext, requesterUserId, showLimits, limitsAtStart, locale }; // #594: showLimits/limitsAtStart
|
|
112
113
|
const iso = await resolveIsolation(perCommandIsolation, ISOLATION_BACKEND, isolationRunner, VERBOSE);
|
|
113
114
|
let result, session, sessionInfo;
|
|
114
115
|
if (iso) {
|
|
@@ -131,9 +132,9 @@ export function buildExecuteAndUpdateMessage(deps) {
|
|
|
131
132
|
}
|
|
132
133
|
if (result.warning) return safeEdit(`ā ļø ${result.warning}`);
|
|
133
134
|
if (result.success) {
|
|
134
|
-
await safeEdit(formatExecutingWorkSessionMessage({ sessionName: session, isolationBackend: iso?.backend || null, infoBlock }));
|
|
135
|
+
await safeEdit(formatExecutingWorkSessionMessage({ sessionName: session, isolationBackend: iso?.backend || null, infoBlock, locale }));
|
|
135
136
|
if (AUTO_WATCH_MESSAGE && commandName === 'solve' && sessionInfo?.isolationBackend) await startAutoTerminalWatchForSession({ bot, ctx, sessionId: session, sessionInfo, verbose: VERBOSE });
|
|
136
|
-
} else await safeEdit(
|
|
137
|
+
} else await safeEdit(`${t('telegram.error_executing_command', { commandName }, { locale })}:\n\n\`\`\`\n${result.error || result.output}\n\`\`\`\n\n${infoBlock}`);
|
|
137
138
|
};
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* @see https://github.com/link-assistant/hive-mind/issues/594
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { lt, resolveLimitLocale } from './limits-i18n.lib.mjs';
|
|
24
|
+
|
|
23
25
|
const SHOW_LIMITS_FLAG = '--show-limits';
|
|
24
26
|
const NO_SHOW_LIMITS_FLAG = '--no-show-limits';
|
|
25
27
|
|
|
@@ -106,9 +108,9 @@ function pct(value) {
|
|
|
106
108
|
return Math.floor(num);
|
|
107
109
|
}
|
|
108
110
|
|
|
109
|
-
function formatPercentage(value) {
|
|
111
|
+
function formatPercentage(value, locale = null) {
|
|
110
112
|
const p = pct(value);
|
|
111
|
-
return p === null ? '
|
|
113
|
+
return p === null ? lt('na', {}, { locale }) : `${p}%`;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
/**
|
|
@@ -118,26 +120,28 @@ function formatPercentage(value) {
|
|
|
118
120
|
* @param {Object} snapshot - Result of captureLimitsSnapshot for tool=claude
|
|
119
121
|
* @returns {string}
|
|
120
122
|
*/
|
|
121
|
-
function formatClaudeSnapshotCompact(snapshot) {
|
|
122
|
-
|
|
123
|
-
if (!snapshot
|
|
123
|
+
function formatClaudeSnapshotCompact(snapshot, options = {}) {
|
|
124
|
+
const locale = resolveLimitLocale(options);
|
|
125
|
+
if (!snapshot) return `${lt('claude_limits', {}, { locale })}: ${lt('na', {}, { locale })}`;
|
|
126
|
+
if (!snapshot.success) return `${lt('claude_limits', {}, { locale })}: ${snapshot.error || lt('unavailable', {}, { locale })}`;
|
|
124
127
|
const usage = snapshot.usage || {};
|
|
125
128
|
const lines = [];
|
|
126
|
-
lines.push(
|
|
127
|
-
lines.push(
|
|
129
|
+
lines.push(`${lt('five_hour_session', {}, { locale })}: ${formatPercentage(usage.currentSession?.percentage, locale)}`);
|
|
130
|
+
lines.push(`${lt('seven_day_all_models', {}, { locale })}: ${formatPercentage(usage.allModels?.percentage, locale)}`);
|
|
128
131
|
if (usage.sonnetOnly && usage.sonnetOnly.percentage !== null && usage.sonnetOnly.percentage !== undefined) {
|
|
129
|
-
lines.push(
|
|
132
|
+
lines.push(`${lt('seven_day_sonnet_only', {}, { locale })}: ${formatPercentage(usage.sonnetOnly.percentage, locale)}`);
|
|
130
133
|
}
|
|
131
134
|
return lines.join('\n');
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
function formatCodexSnapshotCompact(snapshot) {
|
|
135
|
-
|
|
136
|
-
if (!snapshot
|
|
137
|
+
function formatCodexSnapshotCompact(snapshot, options = {}) {
|
|
138
|
+
const locale = resolveLimitLocale(options);
|
|
139
|
+
if (!snapshot) return `${lt('codex_limits', {}, { locale })}: ${lt('na', {}, { locale })}`;
|
|
140
|
+
if (!snapshot.success) return `${lt('codex_limits', {}, { locale })}: ${snapshot.error || lt('unavailable', {}, { locale })}`;
|
|
137
141
|
const usage = snapshot.usage || {};
|
|
138
142
|
const lines = [];
|
|
139
|
-
lines.push(
|
|
140
|
-
lines.push(
|
|
143
|
+
lines.push(`${lt('five_hour_session', {}, { locale })}: ${formatPercentage(usage.currentSession?.percentage, locale)}`);
|
|
144
|
+
lines.push(`${lt('weekly', {}, { locale })}: ${formatPercentage(usage.allModels?.percentage, locale)}`);
|
|
141
145
|
return lines.join('\n');
|
|
142
146
|
}
|
|
143
147
|
|
|
@@ -150,11 +154,12 @@ function formatCodexSnapshotCompact(snapshot) {
|
|
|
150
154
|
* @param {string} [options.title='š Limits at start'] Block title
|
|
151
155
|
* @returns {string}
|
|
152
156
|
*/
|
|
153
|
-
export function formatLimitsSnapshotBlock(snapshot, { title =
|
|
157
|
+
export function formatLimitsSnapshotBlock(snapshot, { title = null, locale = null } = {}) {
|
|
154
158
|
if (!snapshot) return '';
|
|
155
159
|
const heading = snapshot.toolKey === 'codex' ? 'Codex' : 'Claude';
|
|
156
|
-
const
|
|
157
|
-
|
|
160
|
+
const localizedTitle = title || `š ${lt('limits_at_start', {}, { locale })}`;
|
|
161
|
+
const body = snapshot.toolKey === 'codex' ? formatCodexSnapshotCompact(snapshot, { locale }) : formatClaudeSnapshotCompact(snapshot, { locale });
|
|
162
|
+
return `${localizedTitle} (${heading})\n\`\`\`\n${body}\n\`\`\``;
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
function deltaFor(startPct, endPct) {
|
|
@@ -164,8 +169,8 @@ function deltaFor(startPct, endPct) {
|
|
|
164
169
|
return e - s;
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
function formatDeltaValue(delta) {
|
|
168
|
-
if (delta === null || delta === undefined) return '
|
|
172
|
+
function formatDeltaValue(delta, locale = null) {
|
|
173
|
+
if (delta === null || delta === undefined) return lt('na', {}, { locale });
|
|
169
174
|
if (delta === 0) return '±0%';
|
|
170
175
|
const sign = delta > 0 ? '+' : '';
|
|
171
176
|
return `${sign}${delta}%`;
|
|
@@ -177,37 +182,39 @@ function formatDeltaValue(delta) {
|
|
|
177
182
|
*
|
|
178
183
|
* @param {Object|null} startSnapshot
|
|
179
184
|
* @param {Object|null} endSnapshot
|
|
185
|
+
* @param {Object|string} [options]
|
|
180
186
|
* @returns {string}
|
|
181
187
|
*/
|
|
182
|
-
export function formatLimitsDeltaBlock(startSnapshot, endSnapshot) {
|
|
188
|
+
export function formatLimitsDeltaBlock(startSnapshot, endSnapshot, options = {}) {
|
|
183
189
|
if (!startSnapshot || !endSnapshot) return '';
|
|
184
190
|
if (startSnapshot.toolKey !== endSnapshot.toolKey) return '';
|
|
191
|
+
const locale = resolveLimitLocale(options);
|
|
185
192
|
const heading = startSnapshot.toolKey === 'codex' ? 'Codex' : 'Claude';
|
|
186
193
|
const lines = [];
|
|
187
194
|
|
|
188
195
|
if (!startSnapshot.success && !endSnapshot.success) {
|
|
189
|
-
lines.push(
|
|
190
|
-
lines.push(
|
|
196
|
+
lines.push(`${lt('start', {}, { locale })}: ${startSnapshot.error || lt('unavailable', {}, { locale })}`);
|
|
197
|
+
lines.push(`${lt('end', {}, { locale })}: ${endSnapshot.error || lt('unavailable', {}, { locale })}`);
|
|
191
198
|
} else {
|
|
192
199
|
const startUsage = startSnapshot.usage || {};
|
|
193
200
|
const endUsage = endSnapshot.usage || {};
|
|
194
201
|
|
|
195
|
-
const sessionLabel = '
|
|
196
|
-
lines.push(`${sessionLabel}: ${formatPercentage(startUsage.currentSession?.percentage)} ā ${formatPercentage(endUsage.currentSession?.percentage)} (${formatDeltaValue(deltaFor(startUsage.currentSession?.percentage, endUsage.currentSession?.percentage))})`);
|
|
202
|
+
const sessionLabel = lt('five_hour_session', {}, { locale });
|
|
203
|
+
lines.push(`${sessionLabel}: ${formatPercentage(startUsage.currentSession?.percentage, locale)} ā ${formatPercentage(endUsage.currentSession?.percentage, locale)} (${formatDeltaValue(deltaFor(startUsage.currentSession?.percentage, endUsage.currentSession?.percentage), locale)})`);
|
|
197
204
|
|
|
198
|
-
const allModelsLabel = startSnapshot.toolKey === 'codex' ? '
|
|
199
|
-
lines.push(`${allModelsLabel}: ${formatPercentage(startUsage.allModels?.percentage)} ā ${formatPercentage(endUsage.allModels?.percentage)} (${formatDeltaValue(deltaFor(startUsage.allModels?.percentage, endUsage.allModels?.percentage))})`);
|
|
205
|
+
const allModelsLabel = startSnapshot.toolKey === 'codex' ? lt('weekly', {}, { locale }) : lt('seven_day_all_models', {}, { locale });
|
|
206
|
+
lines.push(`${allModelsLabel}: ${formatPercentage(startUsage.allModels?.percentage, locale)} ā ${formatPercentage(endUsage.allModels?.percentage, locale)} (${formatDeltaValue(deltaFor(startUsage.allModels?.percentage, endUsage.allModels?.percentage), locale)})`);
|
|
200
207
|
|
|
201
208
|
if (startSnapshot.toolKey === 'claude' && ((startUsage.sonnetOnly && startUsage.sonnetOnly.percentage !== null && startUsage.sonnetOnly.percentage !== undefined) || (endUsage.sonnetOnly && endUsage.sonnetOnly.percentage !== null && endUsage.sonnetOnly.percentage !== undefined))) {
|
|
202
|
-
lines.push(
|
|
209
|
+
lines.push(`${lt('seven_day_sonnet_only', {}, { locale })}: ${formatPercentage(startUsage.sonnetOnly?.percentage, locale)} ā ${formatPercentage(endUsage.sonnetOnly?.percentage, locale)} (${formatDeltaValue(deltaFor(startUsage.sonnetOnly?.percentage, endUsage.sonnetOnly?.percentage), locale)})`);
|
|
203
210
|
}
|
|
204
211
|
}
|
|
205
212
|
|
|
206
213
|
// Note: delta is not precise because multiple parallel tasks may consume
|
|
207
214
|
// from the same Anthropic/OpenAI budget windows during the run.
|
|
208
|
-
lines.push('
|
|
215
|
+
lines.push(lt('note_delta_approx', {}, { locale }));
|
|
209
216
|
|
|
210
|
-
return `š
|
|
217
|
+
return `š ${lt('limits_change', {}, { locale })} (${heading})\n\`\`\`\n${lines.join('\n')}\n\`\`\``;
|
|
211
218
|
}
|
|
212
219
|
|
|
213
220
|
/**
|
|
@@ -241,10 +248,10 @@ export function appendInfoSection(infoBlock, addition) {
|
|
|
241
248
|
* @param {boolean} options.enabled - Master switch (config.showLimits)
|
|
242
249
|
* @returns {Promise<{ handled: boolean, args: string[], showLimits: boolean }>}
|
|
243
250
|
*/
|
|
244
|
-
export async function handleShowLimitsFlag({ ctx, safeReply, args, enabled }) {
|
|
251
|
+
export async function handleShowLimitsFlag({ ctx, safeReply, args, enabled, locale = null }) {
|
|
245
252
|
const { showLimits, args: stripped } = extractShowLimitsFlag(args);
|
|
246
253
|
if (showLimits === true && !enabled) {
|
|
247
|
-
await safeReply(ctx, '
|
|
254
|
+
await safeReply(ctx, `ā ${lt('disabled_by_admin', {}, { locale })}`, { reply_to_message_id: ctx.message.message_id });
|
|
248
255
|
return { handled: true, args: stripped, showLimits: false };
|
|
249
256
|
}
|
|
250
257
|
return { handled: false, args: stripped, showLimits: showLimits === true && enabled };
|
|
@@ -264,12 +271,12 @@ export async function handleShowLimitsFlag({ ctx, safeReply, args, enabled }) {
|
|
|
264
271
|
* @param {string} [options.commandLabel='command'] - For verbose logging
|
|
265
272
|
* @returns {Promise<{ infoBlock: string, limitsAtStart: Object|null }>}
|
|
266
273
|
*/
|
|
267
|
-
export async function captureStartSnapshotAndAppend({ infoBlock, tool = 'claude', verbose = false, limitsLib, commandLabel = 'command' } = {}) {
|
|
274
|
+
export async function captureStartSnapshotAndAppend({ infoBlock, tool = 'claude', verbose = false, limitsLib, commandLabel = 'command', locale = null } = {}) {
|
|
268
275
|
let limitsAtStart = null;
|
|
269
276
|
let nextInfoBlock = infoBlock || '';
|
|
270
277
|
try {
|
|
271
278
|
limitsAtStart = await captureLimitsSnapshot({ tool, verbose, limitsLib });
|
|
272
|
-
const block = formatLimitsSnapshotBlock(limitsAtStart, {
|
|
279
|
+
const block = formatLimitsSnapshotBlock(limitsAtStart, { locale });
|
|
273
280
|
if (block) nextInfoBlock = appendInfoSection(nextInfoBlock, block);
|
|
274
281
|
} catch (e) {
|
|
275
282
|
if (verbose) console.log(`[VERBOSE] ${commandLabel} --show-limits snapshot failed: ${e?.message || e}`);
|