@link-assistant/hive-mind 1.65.1 ā 1.66.0
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 +2 -1
- package/src/github-rate-limit.lib.mjs +69 -14
- package/src/github.batch.lib.mjs +27 -25
- package/src/github.graphql.lib.mjs +10 -9
- package/src/github.lib.mjs +12 -5
- package/src/hive.mjs +10 -5
- package/src/i18n.lib.mjs +174 -0
- package/src/limits.lib.mjs +3 -2
- package/src/locales/en.lino +93 -0
- package/src/locales/hi.lino +93 -0
- package/src/locales/ru.lino +93 -0
- package/src/locales/zh.lino +93 -0
- package/src/review.mjs +9 -0
- package/src/reviewers-hive.mjs +21 -19
- package/src/solve.auto-pr.lib.mjs +26 -17
- package/src/solve.config.lib.mjs +5 -0
- package/src/solve.mjs +4 -0
- package/src/task.config.lib.mjs +5 -0
- package/src/task.mjs +4 -0
- package/src/telegram-bot.mjs +35 -21
- package/src/telegram-language-command.lib.mjs +49 -0
package/src/task.mjs
CHANGED
|
@@ -50,6 +50,10 @@ try {
|
|
|
50
50
|
process.exit(1);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Initialize i18n based on --language (or detected system locale)
|
|
54
|
+
const { initI18n } = await import('./i18n.lib.mjs');
|
|
55
|
+
await initI18n(argv.language);
|
|
56
|
+
|
|
53
57
|
const taskInput = argv['task-input'] || argv.taskInput || argv._[0];
|
|
54
58
|
const selectedModel = argv.model || getDefaultTaskModel(argv.tool);
|
|
55
59
|
const modelValidation = validateModelName(selectedModel, argv.tool);
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -297,6 +297,12 @@ await initializeSentry({
|
|
|
297
297
|
environment: process.env.NODE_ENV || 'production',
|
|
298
298
|
});
|
|
299
299
|
|
|
300
|
+
// Initialize i18n: pre-load every supported locale so per-user translations
|
|
301
|
+
// can resolve synchronously from the cache when handling Telegram updates.
|
|
302
|
+
const { initI18n, t, preloadAllLocales, resolveLocaleFromTelegramCtx } = await import('./i18n.lib.mjs');
|
|
303
|
+
await initI18n();
|
|
304
|
+
await preloadAllLocales();
|
|
305
|
+
|
|
300
306
|
const telegrafModule = await use('telegraf');
|
|
301
307
|
const { Telegraf } = telegrafModule;
|
|
302
308
|
|
|
@@ -564,6 +570,7 @@ bot.command('help', async ctx => {
|
|
|
564
570
|
message += '`/solve_queue` - Show solve queue status\n';
|
|
565
571
|
message += '*/limits* - Show usage limits\n';
|
|
566
572
|
message += '*/version* - Show bot and runtime versions\n';
|
|
573
|
+
message += '*/language* `[en|ru|zh|hi]` - Set or show your preferred reply language (in-memory only, per-user)\n';
|
|
567
574
|
message += '`/accept_invites` - Accept all pending GitHub invitations\n';
|
|
568
575
|
message += '*/merge* - Merge queue (experimental)\n';
|
|
569
576
|
message += 'Usage: `/merge <github-repo-url>`\n';
|
|
@@ -621,11 +628,12 @@ bot.command('limits', async ctx => {
|
|
|
621
628
|
return;
|
|
622
629
|
}
|
|
623
630
|
|
|
631
|
+
const userLocale = resolveLocaleFromTelegramCtx(ctx);
|
|
624
632
|
if (!_isGroupChat(ctx)) {
|
|
625
633
|
if (VERBOSE) {
|
|
626
634
|
console.log('[VERBOSE] /limits ignored: not a group chat');
|
|
627
635
|
}
|
|
628
|
-
await ctx.reply('
|
|
636
|
+
await ctx.reply(t('telegram.limits_only_in_groups', {}, { locale: userLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
629
637
|
return;
|
|
630
638
|
}
|
|
631
639
|
|
|
@@ -638,7 +646,7 @@ bot.command('limits', async ctx => {
|
|
|
638
646
|
}
|
|
639
647
|
|
|
640
648
|
// Send "fetching" message to indicate work is in progress
|
|
641
|
-
const fetchingMessage = await ctx.reply('
|
|
649
|
+
const fetchingMessage = await ctx.reply(t('telegram.fetching_limits', {}, { locale: userLocale }), {
|
|
642
650
|
reply_to_message_id: ctx.message.message_id,
|
|
643
651
|
});
|
|
644
652
|
|
|
@@ -651,7 +659,7 @@ bot.command('limits', async ctx => {
|
|
|
651
659
|
const solveQueue = getSolveQueue({ verbose: VERBOSE });
|
|
652
660
|
const queueStatus = await solveQueue.formatStatus();
|
|
653
661
|
const codexSection = formatCodexLimitsSection(limits.codex.success ? limits.codex : null, codexError);
|
|
654
|
-
const message = '
|
|
662
|
+
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]);
|
|
655
663
|
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'Markdown' });
|
|
656
664
|
});
|
|
657
665
|
bot.command('version', async ctx => {
|
|
@@ -663,16 +671,20 @@ bot.command('version', async ctx => {
|
|
|
663
671
|
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
664
672
|
});
|
|
665
673
|
if (isOldMessage(ctx) || isForwardedOrReply(ctx)) return;
|
|
666
|
-
|
|
674
|
+
const versionLocale = resolveLocaleFromTelegramCtx(ctx);
|
|
675
|
+
if (!_isGroupChat(ctx)) return await ctx.reply(t('telegram.version_only_in_groups', {}, { locale: versionLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
667
676
|
if (!isTopicAuthorized(ctx)) return await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
|
|
668
|
-
const fetchingMessage = await ctx.reply('
|
|
677
|
+
const fetchingMessage = await ctx.reply(t('telegram.gathering_version', {}, { locale: versionLocale }), {
|
|
669
678
|
reply_to_message_id: ctx.message.message_id,
|
|
670
679
|
});
|
|
671
680
|
const result = await getVersionInfo(VERBOSE);
|
|
672
681
|
if (!result.success) return await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `ā ${escapeMarkdownV2(result.error, { preserveCodeBlocks: true })}`, { parse_mode: 'MarkdownV2' });
|
|
673
|
-
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, '
|
|
682
|
+
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, t('telegram.version_information_title', {}, { locale: versionLocale }) + '\n\n' + formatVersionMessage(result.versions), { parse_mode: 'Markdown' });
|
|
674
683
|
});
|
|
675
684
|
|
|
685
|
+
const { registerLanguageCommand } = await import('./telegram-language-command.lib.mjs');
|
|
686
|
+
registerLanguageCommand(bot, { VERBOSE, isOldMessage, isForwardedOrReply });
|
|
687
|
+
|
|
676
688
|
const { registerAcceptInvitesCommand } = await import('./telegram-accept-invitations.lib.mjs');
|
|
677
689
|
const sharedCommandOpts = { VERBOSE, isOldMessage, isForwardedOrReply, isGroupChat: _isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, isChatStopped, getStoppedChatRejectMessage };
|
|
678
690
|
registerAcceptInvitesCommand(bot, sharedCommandOpts);
|
|
@@ -704,11 +716,12 @@ async function handleSolveCommand(ctx) {
|
|
|
704
716
|
},
|
|
705
717
|
});
|
|
706
718
|
|
|
719
|
+
const solveLocale = resolveLocaleFromTelegramCtx(ctx);
|
|
707
720
|
if (!solveEnabled) {
|
|
708
721
|
if (VERBOSE) {
|
|
709
722
|
console.log(`[VERBOSE] ${solveCommandDisplay} ignored: command disabled`);
|
|
710
723
|
}
|
|
711
|
-
await ctx.reply('
|
|
724
|
+
await ctx.reply(t('telegram.solve_disabled', {}, { locale: solveLocale }));
|
|
712
725
|
return;
|
|
713
726
|
}
|
|
714
727
|
|
|
@@ -737,7 +750,7 @@ async function handleSolveCommand(ctx) {
|
|
|
737
750
|
if (VERBOSE) {
|
|
738
751
|
console.log(`[VERBOSE] ${solveCommandDisplay} ignored: not a group chat`);
|
|
739
752
|
}
|
|
740
|
-
await ctx.reply(
|
|
753
|
+
await ctx.reply(t('telegram.solve_only_in_groups', { commandDisplay: solveCommandDisplay }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
741
754
|
return;
|
|
742
755
|
}
|
|
743
756
|
|
|
@@ -802,7 +815,7 @@ async function handleSolveCommand(ctx) {
|
|
|
802
815
|
if (VERBOSE) {
|
|
803
816
|
console.log('[VERBOSE] No GitHub URL found in replied message');
|
|
804
817
|
}
|
|
805
|
-
await safeReply(ctx, '
|
|
818
|
+
await safeReply(ctx, t('telegram.no_github_link_in_reply', {}, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
806
819
|
return;
|
|
807
820
|
}
|
|
808
821
|
}
|
|
@@ -811,7 +824,7 @@ async function handleSolveCommand(ctx) {
|
|
|
811
824
|
|
|
812
825
|
const { malformed, errors: malformedErrors } = detectMalformedFlags(userArgs);
|
|
813
826
|
if (malformed.length > 0) {
|
|
814
|
-
await safeReply(ctx, `ā ${escapeMarkdown(malformedErrors.join('\n'))}\n\
|
|
827
|
+
await safeReply(ctx, `ā ${escapeMarkdown(malformedErrors.join('\n'))}\n\n${t('telegram.option_syntax_check', {}, { locale: solveLocale })}`, { reply_to_message_id: ctx.message.message_id });
|
|
815
828
|
return;
|
|
816
829
|
}
|
|
817
830
|
|
|
@@ -828,13 +841,13 @@ async function handleSolveCommand(ctx) {
|
|
|
828
841
|
userArgs = moveArgumentToFront(userArgs, validation.normalizedUrl, cleanNonPrintableChars);
|
|
829
842
|
const { backend: solvePerCommandIsolation, filteredArgs: userArgsWithoutIsolation } = extractIsolationFromArgs(userArgs); // issue #1534
|
|
830
843
|
if (solvePerCommandIsolation && !isValidPerCommandIsolation(solvePerCommandIsolation)) {
|
|
831
|
-
await safeReply(ctx,
|
|
844
|
+
await safeReply(ctx, t('telegram.invalid_isolation', { value: escapeMarkdown(solvePerCommandIsolation) }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
832
845
|
return;
|
|
833
846
|
}
|
|
834
847
|
const mergedSolveArgs = mergeArgsWithOverrides(userArgsWithoutIsolation, solveOverrides);
|
|
835
848
|
const { backend: solveOverrideIsolation, filteredArgs: args } = extractIsolationFromArgs(mergedSolveArgs);
|
|
836
849
|
if (solveOverrideIsolation && !isValidPerCommandIsolation(solveOverrideIsolation)) {
|
|
837
|
-
await safeReply(ctx,
|
|
850
|
+
await safeReply(ctx, t('telegram.invalid_locked_isolation', { value: escapeMarkdown(solveOverrideIsolation) }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
838
851
|
return;
|
|
839
852
|
}
|
|
840
853
|
const effectiveSolveIsolation = solveOverrideIsolation || solvePerCommandIsolation;
|
|
@@ -863,7 +876,7 @@ async function handleSolveCommand(ctx) {
|
|
|
863
876
|
}
|
|
864
877
|
const { malformed: mergedMalformed, errors: mergedMalformedErrors } = detectMalformedFlags(args);
|
|
865
878
|
if (mergedMalformed.length > 0) {
|
|
866
|
-
await safeReply(ctx, `ā ${escapeMarkdown(mergedMalformedErrors.join('\n'))}\n\
|
|
879
|
+
await safeReply(ctx, `ā ${escapeMarkdown(mergedMalformedErrors.join('\n'))}\n\n${t('telegram.option_syntax_check', {}, { locale: solveLocale })}`, { reply_to_message_id: ctx.message.message_id });
|
|
867
880
|
return;
|
|
868
881
|
}
|
|
869
882
|
// Validate merged arguments using solve's yargs config
|
|
@@ -871,7 +884,7 @@ async function handleSolveCommand(ctx) {
|
|
|
871
884
|
try {
|
|
872
885
|
parsedSolveArgs = await parseArgsWithYargs(args, yargs, createSolveYargsConfig);
|
|
873
886
|
} catch (error) {
|
|
874
|
-
await safeReply(ctx,
|
|
887
|
+
await safeReply(ctx, t('telegram.invalid_options', { message: escapeMarkdown(error.message || String(error)) }, { locale: solveLocale }), {
|
|
875
888
|
reply_to_message_id: ctx.message.message_id,
|
|
876
889
|
});
|
|
877
890
|
return;
|
|
@@ -909,20 +922,20 @@ async function handleSolveCommand(ctx) {
|
|
|
909
922
|
const existingItem = solveQueue.findByUrl(normalizedUrl);
|
|
910
923
|
if (existingItem) {
|
|
911
924
|
const statusText = existingItem.status === 'starting' || existingItem.status === 'started' ? 'being processed' : 'already in the queue';
|
|
912
|
-
await safeReply(ctx,
|
|
925
|
+
await safeReply(ctx, t('telegram.url_status_active', { statusText, url: escapeMarkdown(normalizedUrl), status: existingItem.status }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
913
926
|
return;
|
|
914
927
|
}
|
|
915
928
|
// Issue #1567: Prevent concurrent sessions on the same PR/issue
|
|
916
929
|
const activeSession = await hasActiveSessionForUrlAsync(normalizedUrl, VERBOSE);
|
|
917
930
|
if (activeSession.isActive) {
|
|
918
|
-
await safeReply(ctx,
|
|
931
|
+
await safeReply(ctx, t('telegram.url_session_running', { url: escapeMarkdown(normalizedUrl), session: activeSession.sessionName }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
919
932
|
return;
|
|
920
933
|
}
|
|
921
934
|
const check = await solveQueue.canStartCommand({ tool: solveTool }); // Skip Claude limits for agent (#1159)
|
|
922
935
|
const queueStats = solveQueue.getStats();
|
|
923
936
|
// Handle rejection: threshold strategy is 'reject' ā fail immediately (issue #1267)
|
|
924
937
|
if (check.rejected) {
|
|
925
|
-
await safeReply(ctx,
|
|
938
|
+
await safeReply(ctx, t('telegram.solve_rejected', { infoBlock, reason: escapeMarkdown(check.rejectReason || 'Unknown') }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
926
939
|
return;
|
|
927
940
|
}
|
|
928
941
|
|
|
@@ -970,11 +983,12 @@ async function handleHiveCommand(ctx) {
|
|
|
970
983
|
},
|
|
971
984
|
});
|
|
972
985
|
|
|
986
|
+
const hiveLocale = resolveLocaleFromTelegramCtx(ctx);
|
|
973
987
|
if (!hiveEnabled) {
|
|
974
988
|
if (VERBOSE) {
|
|
975
989
|
console.log('[VERBOSE] /hive ignored: command disabled');
|
|
976
990
|
}
|
|
977
|
-
await ctx.reply('
|
|
991
|
+
await ctx.reply(t('telegram.hive_disabled', {}, { locale: hiveLocale }));
|
|
978
992
|
return;
|
|
979
993
|
}
|
|
980
994
|
|
|
@@ -998,7 +1012,7 @@ async function handleHiveCommand(ctx) {
|
|
|
998
1012
|
if (VERBOSE) {
|
|
999
1013
|
console.log('[VERBOSE] /hive ignored: not a group chat');
|
|
1000
1014
|
}
|
|
1001
|
-
await ctx.reply('
|
|
1015
|
+
await ctx.reply(t('telegram.hive_only_in_groups', {}, { locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
1002
1016
|
return;
|
|
1003
1017
|
}
|
|
1004
1018
|
|
|
@@ -1041,13 +1055,13 @@ async function handleHiveCommand(ctx) {
|
|
|
1041
1055
|
|
|
1042
1056
|
const { backend: hivePerCommandIsolation, filteredArgs: normalizedArgsWithoutIsolation } = extractIsolationFromArgs(normalizedArgs); // issue #1534
|
|
1043
1057
|
if (hivePerCommandIsolation && !isValidPerCommandIsolation(hivePerCommandIsolation)) {
|
|
1044
|
-
await safeReply(ctx,
|
|
1058
|
+
await safeReply(ctx, t('telegram.invalid_isolation', { value: escapeMarkdown(hivePerCommandIsolation) }, { locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
1045
1059
|
return;
|
|
1046
1060
|
}
|
|
1047
1061
|
const mergedHiveArgs = mergeArgsWithOverrides(normalizedArgsWithoutIsolation, hiveOverrides);
|
|
1048
1062
|
const { backend: hiveOverrideIsolation, filteredArgs: args } = extractIsolationFromArgs(mergedHiveArgs);
|
|
1049
1063
|
if (hiveOverrideIsolation && !isValidPerCommandIsolation(hiveOverrideIsolation)) {
|
|
1050
|
-
await safeReply(ctx,
|
|
1064
|
+
await safeReply(ctx, t('telegram.invalid_locked_isolation', { value: escapeMarkdown(hiveOverrideIsolation) }, { locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
1051
1065
|
return;
|
|
1052
1066
|
}
|
|
1053
1067
|
const effectiveHiveIsolation = hiveOverrideIsolation || hivePerCommandIsolation;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram /language command implementation.
|
|
3
|
+
*
|
|
4
|
+
* Allows each user to override the bot's reply language for the current
|
|
5
|
+
* process. The override is in-memory only (resets when the bot restarts).
|
|
6
|
+
*
|
|
7
|
+
* Usage in chat:
|
|
8
|
+
* /language -> show current language
|
|
9
|
+
* /language <en|ru|zh|hi> -> set language for this user
|
|
10
|
+
* /language default -> clear the override (reset|clear also work)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { t, getSupportedLocales, normalizeLocale, setUserLocale, clearUserLocale, resolveLocaleFromTelegramCtx } from './i18n.lib.mjs';
|
|
14
|
+
|
|
15
|
+
export function registerLanguageCommand(bot, options = {}) {
|
|
16
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply } = options;
|
|
17
|
+
|
|
18
|
+
bot.command('language', async ctx => {
|
|
19
|
+
VERBOSE && console.log('[VERBOSE] /language command received');
|
|
20
|
+
if (isOldMessage?.(ctx) || isForwardedOrReply?.(ctx)) return;
|
|
21
|
+
const userId = ctx.from?.id;
|
|
22
|
+
const locale = resolveLocaleFromTelegramCtx(ctx);
|
|
23
|
+
const supported = getSupportedLocales();
|
|
24
|
+
const supportedList = supported.join(', ');
|
|
25
|
+
const text = ctx.message?.text || '';
|
|
26
|
+
const parts = text.trim().split(/\s+/);
|
|
27
|
+
const arg = parts.length > 1 ? parts[1] : null;
|
|
28
|
+
if (!arg) {
|
|
29
|
+
const langName = t(`language.${locale}`, {}, { locale });
|
|
30
|
+
await ctx.reply(t('telegram.language_current', { language: langName, supported: supportedList }, { locale }), { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (['default', 'reset', 'clear'].includes(arg.toLowerCase())) {
|
|
34
|
+
clearUserLocale(userId);
|
|
35
|
+
const newLocale = resolveLocaleFromTelegramCtx(ctx);
|
|
36
|
+
const langName = t(`language.${newLocale}`, {}, { locale: newLocale });
|
|
37
|
+
await ctx.reply(t('telegram.language_set', { language: langName }, { locale: newLocale }), { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const target = normalizeLocale(arg);
|
|
41
|
+
if (!target) {
|
|
42
|
+
await ctx.reply(t('telegram.language_invalid', { supported: supportedList }, { locale }), { reply_to_message_id: ctx.message.message_id });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setUserLocale(userId, target);
|
|
46
|
+
const langName = t(`language.${target}`, {}, { locale: target });
|
|
47
|
+
await ctx.reply(t('telegram.language_set', { language: langName }, { locale: target }), { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
48
|
+
});
|
|
49
|
+
}
|