@link-assistant/hive-mind 2.11.13 ā 2.12.1
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 +18 -0
- package/package.json +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
package/src/telegram-bot.mjs
CHANGED
|
@@ -13,14 +13,12 @@ if (process.argv.includes('--version')) {
|
|
|
13
13
|
if (typeof use === 'undefined') {
|
|
14
14
|
await ensureUseM();
|
|
15
15
|
}
|
|
16
|
-
|
|
17
16
|
const { lino } = await import('./lino.lib.mjs');
|
|
18
17
|
const { loadLenvConfig } = await import('./lenv-reader.lib.mjs');
|
|
19
18
|
const { getLinoYargsFactory, getenv, hideBin } = await import('./cli-arguments.lib.mjs');
|
|
20
19
|
|
|
21
20
|
const dotenvxModule = await use('@dotenvx/dotenvx');
|
|
22
21
|
const dotenvx = dotenvxModule.default || dotenvxModule;
|
|
23
|
-
|
|
24
22
|
// Load .env/.lenv configuration (issue #1318)
|
|
25
23
|
dotenvx.config({ quiet: true, ignore: ['MISSING_ENV_FILE'] });
|
|
26
24
|
await loadLenvConfig({ override: true, quiet: true });
|
|
@@ -33,14 +31,12 @@ const { enhanceUnknownArgumentError } = await import('./option-suggestions.lib.m
|
|
|
33
31
|
const { validateBranchInArgs } = await import('./solve.branch.lib.mjs');
|
|
34
32
|
const { extractIsolationFromArgs, isValidPerCommandIsolation } = await import('./telegram-isolation.lib.mjs');
|
|
35
33
|
const { mergeArgsWithOverrides } = await import('./args-overrides.lib.mjs'); // issue #2085
|
|
36
|
-
|
|
37
34
|
const config = createTelegramYargsConfig(yargs(hideBin(process.argv))).parse();
|
|
38
35
|
|
|
39
36
|
// Configuration priority: CLI option > --configuration LINO > .lenv > .env
|
|
40
37
|
if (config.configuration) {
|
|
41
38
|
await loadLenvConfig({ configuration: config.configuration, override: true, quiet: true });
|
|
42
39
|
}
|
|
43
|
-
|
|
44
40
|
const BOT_TOKEN = config.token || getenv('TELEGRAM_BOT_TOKEN', '');
|
|
45
41
|
const VERBOSE = config.verbose || getenv('TELEGRAM_BOT_VERBOSE', 'false') === 'true';
|
|
46
42
|
const AUTO_WATCH_MESSAGE = config.autoStartScreenWatchMessage === true;
|
|
@@ -53,7 +49,6 @@ if (!BOT_TOKEN) {
|
|
|
53
49
|
// Resolve final config values (CLI option > environment variable)
|
|
54
50
|
const resolvedAllowedChats = config.allowedChats || getenv('TELEGRAM_ALLOWED_CHATS', '');
|
|
55
51
|
const allowedChats = resolvedAllowedChats ? lino.parseNumericIds(resolvedAllowedChats) : null;
|
|
56
|
-
|
|
57
52
|
// Parse allowed topics (chatId:topicId pairs in Links Notation)
|
|
58
53
|
const resolvedAllowedTopics = config.allowedTopics || getenv('TELEGRAM_ALLOWED_TOPICS', '');
|
|
59
54
|
const allowedTopics = resolvedAllowedTopics ? lino.parseLinks(resolvedAllowedTopics) : null;
|
|
@@ -125,7 +120,6 @@ if (solveEnabled && solveOverrides.length > 0) {
|
|
|
125
120
|
}
|
|
126
121
|
// Add a dummy URL as the first argument (required positional for solve)
|
|
127
122
|
const testArgs = ['https://github.com/test/test/issues/1', ...solveOverridesForValidation];
|
|
128
|
-
|
|
129
123
|
// Temporarily suppress stderr to avoid yargs error output during validation
|
|
130
124
|
const originalStderrWrite = process.stderr.write;
|
|
131
125
|
const stderrBuffer = [];
|
|
@@ -161,7 +155,6 @@ if (solveEnabled && solveOverrides.length > 0) {
|
|
|
161
155
|
process.exit(1);
|
|
162
156
|
}
|
|
163
157
|
}
|
|
164
|
-
|
|
165
158
|
// Validate hive overrides early using hive's yargs config
|
|
166
159
|
// Only validate if hive command is enabled
|
|
167
160
|
if (hiveEnabled && hiveOverrides.length > 0) {
|
|
@@ -181,7 +174,6 @@ if (hiveEnabled && hiveOverrides.length > 0) {
|
|
|
181
174
|
stderrBuffer.push(chunk);
|
|
182
175
|
return true;
|
|
183
176
|
};
|
|
184
|
-
|
|
185
177
|
try {
|
|
186
178
|
// Use .parse() instead of yargs(args).parseSync() to ensure .strict() mode works
|
|
187
179
|
const testYargs = createHiveYargsConfig(yargs());
|
|
@@ -234,7 +226,6 @@ if (config.dryRun) {
|
|
|
234
226
|
console.log('\nš Bot configuration is valid. Exiting without starting the bot.');
|
|
235
227
|
process.exit(0);
|
|
236
228
|
}
|
|
237
|
-
|
|
238
229
|
// === HEAVY DEPENDENCIES LOADED BELOW (skipped in dry-run mode) ===
|
|
239
230
|
// These imports are after dry-run check to speed up config validation. Telegraf can take 3-8s to load on cold start (issue #801).
|
|
240
231
|
|
|
@@ -263,7 +254,7 @@ const { createSessionStore } = await import('./session-store.lib.mjs');
|
|
|
263
254
|
const { createHeartbeat, resumeSessionsOnLaunch, createShutdownHandler } = await import('./bot-lifecycle.lib.mjs');
|
|
264
255
|
const { formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage } = await import('./work-session-formatting.lib.mjs');
|
|
265
256
|
const { buildTelegramHelpMessage, buildTelegramInfoBlock, buildSolveQueuedMessage } = await import('./telegram-ui-messages.lib.mjs');
|
|
266
|
-
|
|
257
|
+
const { startFormalAiMaintenance } = await import('./formal-ai-maintenance.lib.mjs');
|
|
267
258
|
// Initialize Sentry for error tracking
|
|
268
259
|
await initializeSentry({
|
|
269
260
|
debug: VERBOSE,
|
|
@@ -275,7 +266,6 @@ await initializeSentry({
|
|
|
275
266
|
const { initI18n, t, preloadAllLocales, resolveLocaleFromTelegramCtx } = await import('./i18n.lib.mjs');
|
|
276
267
|
await initI18n();
|
|
277
268
|
await preloadAllLocales();
|
|
278
|
-
|
|
279
269
|
const telegrafModule = await use('telegraf');
|
|
280
270
|
const { Telegraf } = telegrafModule;
|
|
281
271
|
const bot = new Telegraf(BOT_TOKEN, {
|
|
@@ -301,7 +291,6 @@ botLogger.event('bot_starting', { pid: process.pid, ppid: process.ppid, botStart
|
|
|
301
291
|
function isChatAuthorized(chatId) {
|
|
302
292
|
return _isChatAuthorized(chatId, allowedChats);
|
|
303
293
|
}
|
|
304
|
-
|
|
305
294
|
// Topic-level authorization (issue #1100): chat-level auth overrides topic-level
|
|
306
295
|
function isTopicAuthorized(ctx) {
|
|
307
296
|
if (isChatAuthorized(ctx.chat?.id)) return true;
|
|
@@ -321,7 +310,6 @@ function buildAuthErrorMessage(ctx) {
|
|
|
321
310
|
function isOldMessage(ctx) {
|
|
322
311
|
return _isOldMessage(ctx, BOT_START_TIME, { verbose: VERBOSE });
|
|
323
312
|
}
|
|
324
|
-
|
|
325
313
|
async function executeStartScreen(command, args) {
|
|
326
314
|
return executeStartScreenCommand(command, args, { verbose: VERBOSE });
|
|
327
315
|
}
|
|
@@ -329,7 +317,6 @@ async function executeStartScreen(command, args) {
|
|
|
329
317
|
function isForwardedOrReply(ctx) {
|
|
330
318
|
return _isForwardedOrReply(ctx, { verbose: VERBOSE });
|
|
331
319
|
}
|
|
332
|
-
|
|
333
320
|
// Forwarded-only check (issue #1922). Commands that support the reply feature
|
|
334
321
|
// (e.g. /task, /solve) must still reject forwarded commands without rejecting
|
|
335
322
|
// genuine user replies, so they use this instead of isForwardedOrReply.
|
|
@@ -370,7 +357,6 @@ function validateModelInArgs(args, tool = 'claude') {
|
|
|
370
357
|
}
|
|
371
358
|
return null;
|
|
372
359
|
}
|
|
373
|
-
|
|
374
360
|
// Inject --language LOCALE into spawn args if no language flag is already present.
|
|
375
361
|
// Issue #378: telegram bot resolves the user's effective locale and propagates
|
|
376
362
|
// it to spawned solve/hive sessions so the AI tool replies in the same language.
|
|
@@ -390,7 +376,6 @@ async function getCommandUrlArg(args, createYargsConfig, positionalNames) {
|
|
|
390
376
|
if (parsedUrl) return parsedUrl;
|
|
391
377
|
return args.find(arg => cleanNonPrintableChars(arg).includes('github.com')) || (args[0] && !args[0].startsWith('-') ? args[0] : null);
|
|
392
378
|
}
|
|
393
|
-
|
|
394
379
|
async function validateGitHubUrl(args, options = {}) {
|
|
395
380
|
const { allowedTypes = ['issue', 'pull'], commandName = 'solve', createYargsConfig = null, positionalNames = [], locale = null } = options;
|
|
396
381
|
const rawUrl = await getCommandUrlArg(args, createYargsConfig, positionalNames);
|
|
@@ -416,7 +401,6 @@ async function validateGitHubUrl(args, options = {}) {
|
|
|
416
401
|
}
|
|
417
402
|
|
|
418
403
|
const executeAndUpdateMessage = buildExecuteAndUpdateMessage({ resolveIsolation, ISOLATION_BACKEND, isolationRunner, VERBOSE, executeStartScreen, trackSession, untrackSession, AUTO_WATCH_MESSAGE, startAutoTerminalWatchForSession, bot, formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage });
|
|
419
|
-
|
|
420
404
|
bot.command('help', async ctx => {
|
|
421
405
|
VERBOSE && console.log('[VERBOSE] /help command received');
|
|
422
406
|
|
|
@@ -425,7 +409,6 @@ bot.command('help', async ctx => {
|
|
|
425
409
|
VERBOSE && console.log('[VERBOSE] /help ignored: old message');
|
|
426
410
|
return;
|
|
427
411
|
}
|
|
428
|
-
|
|
429
412
|
// Ignore forwarded or reply messages
|
|
430
413
|
if (isForwardedOrReply(ctx)) {
|
|
431
414
|
VERBOSE && console.log('[VERBOSE] /help ignored: forwarded or reply');
|
|
@@ -463,13 +446,11 @@ bot.command('help', async ctx => {
|
|
|
463
446
|
authorized,
|
|
464
447
|
allowTopicHint: topicId ? `TELEGRAM_ALLOWED_TOPICS="(${chatId} ${topicId})"` : '',
|
|
465
448
|
});
|
|
466
|
-
|
|
467
449
|
await safeReply(ctx, message, { fallbackLocale: helpLocale });
|
|
468
450
|
});
|
|
469
451
|
|
|
470
452
|
bot.command('limits', async ctx => {
|
|
471
453
|
VERBOSE && console.log('[VERBOSE] /limits command received');
|
|
472
|
-
|
|
473
454
|
// Add breadcrumb for error tracking
|
|
474
455
|
await addBreadcrumb({ category: 'telegram.command', message: '/limits command received', level: 'info', data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username } });
|
|
475
456
|
|
|
@@ -478,7 +459,6 @@ bot.command('limits', async ctx => {
|
|
|
478
459
|
VERBOSE && console.log('[VERBOSE] /limits ignored: old message');
|
|
479
460
|
return;
|
|
480
461
|
}
|
|
481
|
-
|
|
482
462
|
// Ignore forwarded or reply messages
|
|
483
463
|
if (isForwardedOrReply(ctx)) {
|
|
484
464
|
VERBOSE && console.log('[VERBOSE] /limits ignored: forwarded or reply');
|
|
@@ -493,7 +473,6 @@ bot.command('limits', async ctx => {
|
|
|
493
473
|
await ctx.reply(t('telegram.limits_only_in_groups', {}, { locale: userLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
494
474
|
return;
|
|
495
475
|
}
|
|
496
|
-
|
|
497
476
|
if (!isTopicAuthorized(ctx)) {
|
|
498
477
|
if (VERBOSE) {
|
|
499
478
|
console.log('[VERBOSE] /limits ignored: not authorized');
|
|
@@ -509,7 +488,6 @@ bot.command('limits', async ctx => {
|
|
|
509
488
|
|
|
510
489
|
// Get all limits using shared cache (3min for API, 2min for system)
|
|
511
490
|
const limits = await getAllCachedLimits(VERBOSE);
|
|
512
|
-
|
|
513
491
|
// Format message with usage limits and queue status (issues #1343, #1267)
|
|
514
492
|
const claudeError = limits.claude.success ? null : limits.claude.error;
|
|
515
493
|
const codexError = limits.codex.success ? null : limits.codex.error;
|
|
@@ -543,7 +521,6 @@ bot.command('version', async ctx => {
|
|
|
543
521
|
|
|
544
522
|
const { registerLanguageCommand } = await import('./telegram-language-command.lib.mjs');
|
|
545
523
|
registerLanguageCommand(bot, { VERBOSE, isOldMessage, isForwardedOrReply });
|
|
546
|
-
|
|
547
524
|
const { registerAcceptInvitesCommand } = await import('./telegram-accept-invitations.lib.mjs');
|
|
548
525
|
const sharedCommandOpts = { VERBOSE, isOldMessage, isForwarded, isForwardedOrReply, isGroupChat: _isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, isChatStopped, getStoppedChatRejectMessage };
|
|
549
526
|
registerAcceptInvitesCommand(bot, sharedCommandOpts);
|
|
@@ -565,7 +542,6 @@ async function handleSolveCommand(ctx) {
|
|
|
565
542
|
const solveCommandName = getSolveCommandNameFromText(ctx.message?.text) || 'solve';
|
|
566
543
|
const solveCommandDisplay = `/${solveCommandName}`;
|
|
567
544
|
VERBOSE && console.log(`[VERBOSE] ${solveCommandDisplay} command received`);
|
|
568
|
-
|
|
569
545
|
// Add breadcrumb for error tracking
|
|
570
546
|
await addBreadcrumb({
|
|
571
547
|
category: 'telegram.command',
|
|
@@ -587,7 +563,6 @@ async function handleSolveCommand(ctx) {
|
|
|
587
563
|
await ctx.reply(t('telegram.solve_disabled', {}, { locale: solveLocale }));
|
|
588
564
|
return;
|
|
589
565
|
}
|
|
590
|
-
|
|
591
566
|
// Ignore messages sent before bot started
|
|
592
567
|
if (isOldMessage(ctx)) {
|
|
593
568
|
if (VERBOSE) {
|
|
@@ -605,7 +580,6 @@ async function handleSolveCommand(ctx) {
|
|
|
605
580
|
}
|
|
606
581
|
return;
|
|
607
582
|
}
|
|
608
|
-
|
|
609
583
|
if (!_isGroupChat(ctx)) {
|
|
610
584
|
if (VERBOSE) {
|
|
611
585
|
console.log(`[VERBOSE] ${solveCommandDisplay} ignored: not a group chat`);
|
|
@@ -621,7 +595,6 @@ async function handleSolveCommand(ctx) {
|
|
|
621
595
|
await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
|
|
622
596
|
return;
|
|
623
597
|
}
|
|
624
|
-
|
|
625
598
|
// Check if chat is stopped (issue #1081) - reject with same style as queue rejected mode
|
|
626
599
|
const chatId = ctx.chat.id;
|
|
627
600
|
if (isChatStopped(chatId)) {
|
|
@@ -631,7 +604,6 @@ async function handleSolveCommand(ctx) {
|
|
|
631
604
|
}
|
|
632
605
|
|
|
633
606
|
VERBOSE && console.log(`[VERBOSE] ${solveCommandDisplay} passed all checks, executing...`);
|
|
634
|
-
|
|
635
607
|
const solveToolAlias = getSolveToolAliasFromText(ctx.message.text);
|
|
636
608
|
let userArgs = parseCommandArgs(ctx.message.text);
|
|
637
609
|
|
|
@@ -640,7 +612,6 @@ async function handleSolveCommand(ctx) {
|
|
|
640
612
|
if (solveSL.handled) return;
|
|
641
613
|
const solveShowLimits = solveSL.showLimits;
|
|
642
614
|
userArgs = solveSL.args;
|
|
643
|
-
|
|
644
615
|
// Check if this is a reply to a message and user didn't provide URL as first argument
|
|
645
616
|
// In that case, try to extract GitHub URL from the replied message
|
|
646
617
|
// Issue #1325: Support all options via /solve command when replying (e.g., "/solve --model opus")
|
|
@@ -650,7 +621,6 @@ async function handleSolveCommand(ctx) {
|
|
|
650
621
|
const commandUrlArg = await getCommandUrlArg(userArgs, createSolveYargsConfig, ['issue-url']);
|
|
651
622
|
const commandUrlText = commandUrlArg ? cleanNonPrintableChars(commandUrlArg) : '';
|
|
652
623
|
const commandHasUrl = commandUrlText.includes('github.com') || /^https?:\/\//.test(commandUrlText);
|
|
653
|
-
|
|
654
624
|
if (isReply && !commandHasUrl) {
|
|
655
625
|
if (VERBOSE) {
|
|
656
626
|
console.log('[VERBOSE] /solve is a reply without URL in args, extracting from replied message...');
|
|
@@ -659,7 +629,6 @@ async function handleSolveCommand(ctx) {
|
|
|
659
629
|
|
|
660
630
|
const replyText = message.reply_to_message.text || '';
|
|
661
631
|
const extraction = _extractGitHubUrl(replyText, { parseGitHubUrl, cleanNonPrintableChars });
|
|
662
|
-
|
|
663
632
|
if (extraction.error) {
|
|
664
633
|
// Multiple links found
|
|
665
634
|
if (VERBOSE) {
|
|
@@ -687,7 +656,6 @@ async function handleSolveCommand(ctx) {
|
|
|
687
656
|
}
|
|
688
657
|
|
|
689
658
|
userArgs = applySolveToolAlias(userArgs, solveToolAlias);
|
|
690
|
-
|
|
691
659
|
const { malformed, errors: malformedErrors } = detectMalformedFlags(userArgs);
|
|
692
660
|
if (malformed.length > 0) {
|
|
693
661
|
await safeReply(ctx, `ā ${escapeMarkdown(malformedErrors.join('\n'))}\n\n${t('telegram.option_syntax_check', {}, { locale: solveLocale })}`, { reply_to_message_id: ctx.message.message_id });
|
|
@@ -727,7 +695,6 @@ async function handleSolveCommand(ctx) {
|
|
|
727
695
|
solveTool = args[i].substring('--tool='.length);
|
|
728
696
|
}
|
|
729
697
|
}
|
|
730
|
-
|
|
731
698
|
// Validate model name with helpful error message (before yargs validation)
|
|
732
699
|
const modelError = validateModelInArgs(args, solveTool);
|
|
733
700
|
if (modelError) {
|
|
@@ -787,7 +754,6 @@ async function handleSolveCommand(ctx) {
|
|
|
787
754
|
lockedOptions: solveOverrides.length > 0 ? escapeMarkdown(solveOverrides.join(' ')) : '',
|
|
788
755
|
});
|
|
789
756
|
const solveQueue = getSolveQueue({ verbose: VERBOSE });
|
|
790
|
-
|
|
791
757
|
// Check for duplicate URL in queue (issue #1080)
|
|
792
758
|
const existingItem = solveQueue.findByUrl(normalizedUrl);
|
|
793
759
|
if (existingItem) {
|
|
@@ -812,7 +778,6 @@ async function handleSolveCommand(ctx) {
|
|
|
812
778
|
|
|
813
779
|
// Issue #1688: parsed URL context lets the completion message look up linked PRs.
|
|
814
780
|
const solveUrlContext = validation.parsed ? { owner: validation.parsed.owner, repo: validation.parsed.repo, number: validation.parsed.number, type: validation.parsed.type, normalized: validation.parsed.normalized || normalizedUrl } : null;
|
|
815
|
-
|
|
816
781
|
const toolQueuedCount = queueStats.queuedByTool[solveTool] || 0; // tool-specific queue count (#1551)
|
|
817
782
|
// Issue #378: propagate user's effective Telegram locale to the spawned solve session.
|
|
818
783
|
const argsWithLocale = injectLanguageIfMissing(args, solveLocale);
|
|
@@ -820,7 +785,6 @@ async function handleSolveCommand(ctx) {
|
|
|
820
785
|
// Issue #594: append "Limits at start" to infoBlock; thread snapshot via sessionInfo.
|
|
821
786
|
let solveLimitsAtStart = null;
|
|
822
787
|
if (solveShowLimits) ({ infoBlock, limitsAtStart: solveLimitsAtStart } = await captureStartSnapshotAndAppend({ infoBlock, tool: solveTool, verbose: VERBOSE, limitsLib, commandLabel: '/solve', locale: solveLocale }));
|
|
823
|
-
|
|
824
788
|
if (check.canStart && check.startReserved) {
|
|
825
789
|
const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock, locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
826
790
|
await executeAndUpdateMessage(ctx, startingMessage, 'solve', argsWithLocale, infoBlock, effectiveSolveIsolation, solveTool, solveUrlContext, { showLimits: solveShowLimits, limitsAtStart: solveLimitsAtStart, locale: solveLocale, commandAlias: solveCommandName });
|
|
@@ -840,7 +804,6 @@ bot.command(
|
|
|
840
804
|
SOLVE_COMMAND_NAMES.map(command => new RegExp(`^${command}$`, 'i')),
|
|
841
805
|
handleSolveCommand
|
|
842
806
|
);
|
|
843
|
-
|
|
844
807
|
// Named handler for /hive command - extracted for reuse by text-based fallback (issue #1207)
|
|
845
808
|
async function handleHiveCommand(ctx) {
|
|
846
809
|
if (VERBOSE) {
|
|
@@ -859,7 +822,6 @@ async function handleHiveCommand(ctx) {
|
|
|
859
822
|
username: ctx.from?.username,
|
|
860
823
|
},
|
|
861
824
|
});
|
|
862
|
-
|
|
863
825
|
const hiveLocale = resolveLocaleFromTelegramCtx(ctx);
|
|
864
826
|
if (!hiveEnabled) {
|
|
865
827
|
if (VERBOSE) {
|
|
@@ -876,7 +838,6 @@ async function handleHiveCommand(ctx) {
|
|
|
876
838
|
}
|
|
877
839
|
return;
|
|
878
840
|
}
|
|
879
|
-
|
|
880
841
|
// Ignore forwarded or reply messages
|
|
881
842
|
if (isForwardedOrReply(ctx)) {
|
|
882
843
|
if (VERBOSE) {
|
|
@@ -892,7 +853,6 @@ async function handleHiveCommand(ctx) {
|
|
|
892
853
|
await ctx.reply(t('telegram.hive_only_in_groups', {}, { locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
893
854
|
return;
|
|
894
855
|
}
|
|
895
|
-
|
|
896
856
|
if (!isTopicAuthorized(ctx)) {
|
|
897
857
|
if (VERBOSE) {
|
|
898
858
|
console.log('[VERBOSE] /hive ignored: not authorized');
|
|
@@ -908,11 +868,9 @@ async function handleHiveCommand(ctx) {
|
|
|
908
868
|
await safeReply(ctx, getStoppedChatRejectMessage(chatId, 'Hive'), { reply_to_message_id: ctx.message.message_id });
|
|
909
869
|
return;
|
|
910
870
|
}
|
|
911
|
-
|
|
912
871
|
VERBOSE && console.log('[VERBOSE] /hive passed all checks, executing...');
|
|
913
872
|
|
|
914
873
|
let userArgs = parseCommandArgs(ctx.message.text);
|
|
915
|
-
|
|
916
874
|
// Issue #594: see /solve handler.
|
|
917
875
|
const hiveSL = await handleShowLimitsFlag({ ctx, safeReply, args: userArgs, enabled: SHOW_LIMITS_ENABLED, locale: hiveLocale });
|
|
918
876
|
if (hiveSL.handled) return;
|
|
@@ -935,7 +893,6 @@ async function handleHiveCommand(ctx) {
|
|
|
935
893
|
normalizedArgs[0] = `https://github.com/${p.owner}/${p.repo}`;
|
|
936
894
|
if (VERBOSE) console.log(`[VERBOSE] /hive: Normalized ${p.type} URL to repo URL: ${normalizedArgs[0]}`);
|
|
937
895
|
} else if (validation.normalizedUrl && validation.normalizedUrl !== userArgs[0]) normalizedArgs[0] = validation.normalizedUrl;
|
|
938
|
-
|
|
939
896
|
const { backend: hivePerCommandIsolation, filteredArgs: normalizedArgsWithoutIsolation } = extractIsolationFromArgs(normalizedArgs); // issue #1534
|
|
940
897
|
if (hivePerCommandIsolation && !isValidPerCommandIsolation(hivePerCommandIsolation)) {
|
|
941
898
|
await safeReply(ctx, t('telegram.invalid_isolation', { value: escapeMarkdown(hivePerCommandIsolation) }, { locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
@@ -971,7 +928,6 @@ async function handleHiveCommand(ctx) {
|
|
|
971
928
|
await safeReply(ctx, `ā ${escapeMarkdown(hiveBranchError)}`, { reply_to_message_id: ctx.message.message_id });
|
|
972
929
|
return;
|
|
973
930
|
}
|
|
974
|
-
|
|
975
931
|
// Validate merged arguments using hive's yargs config
|
|
976
932
|
try {
|
|
977
933
|
await parseArgsWithYargs(args, yargs, createHiveYargsConfig);
|
|
@@ -994,7 +950,6 @@ async function handleHiveCommand(ctx) {
|
|
|
994
950
|
optionsRaw: userOptionsRaw ? escapeMarkdown(userOptionsRaw) : '',
|
|
995
951
|
lockedOptions: hiveOverrides.length > 0 ? escapeMarkdown(hiveOverrides.join(' ')) : '',
|
|
996
952
|
});
|
|
997
|
-
|
|
998
953
|
// Issue #594: see /solve handler.
|
|
999
954
|
let hiveLimitsAtStart = null;
|
|
1000
955
|
if (hiveShowLimits) ({ infoBlock, limitsAtStart: hiveLimitsAtStart } = await captureStartSnapshotAndAppend({ infoBlock, tool: hiveTool, verbose: VERBOSE, limitsLib, commandLabel: '/hive', locale: hiveLocale }));
|
|
@@ -1004,7 +959,6 @@ async function handleHiveCommand(ctx) {
|
|
|
1004
959
|
const hiveArgsWithLocale = injectLanguageIfMissing(args, hiveLocale);
|
|
1005
960
|
await executeAndUpdateMessage(ctx, startingMessage, 'hive', hiveArgsWithLocale, infoBlock, effectiveHiveIsolation, hiveTool, null, { showLimits: hiveShowLimits, limitsAtStart: hiveLimitsAtStart, locale: hiveLocale });
|
|
1006
961
|
}
|
|
1007
|
-
|
|
1008
962
|
bot.command(/^hive$/i, handleHiveCommand);
|
|
1009
963
|
|
|
1010
964
|
const { registerTopCommand } = await import('./telegram-top-command.lib.mjs');
|
|
@@ -1014,7 +968,6 @@ registerTopCommand(bot, sharedCommandOpts);
|
|
|
1014
968
|
registerStartStopCommands(bot, { ...sharedCommandOpts, getSolveQueue, findRunningSessionByUrl: (url, verbose) => findStoppableSessionByUrl(url, verbose) });
|
|
1015
969
|
await registerLogCommand(bot, sharedCommandOpts);
|
|
1016
970
|
await registerTerminalWatchCommand(bot, sharedCommandOpts);
|
|
1017
|
-
|
|
1018
971
|
// Issue #1745: hidden /tokens command for chat owners (private DMs only,
|
|
1019
972
|
// undocumented, masked output). Lets operators audit which local tokens are
|
|
1020
973
|
// live in the bot's environment so they can search for accidental leaks.
|
|
@@ -1054,7 +1007,6 @@ registerLeakNotifier(async ({ owner, repo, prNumber, tokenHits = [] }) => {
|
|
|
1054
1007
|
}
|
|
1055
1008
|
}
|
|
1056
1009
|
});
|
|
1057
|
-
|
|
1058
1010
|
// Add message listener for verbose debugging
|
|
1059
1011
|
if (VERBOSE) {
|
|
1060
1012
|
bot.on('message', (ctx, next) => {
|
|
@@ -1102,7 +1054,6 @@ if (VERBOSE) {
|
|
|
1102
1054
|
bot.on('message', async (ctx, next) => {
|
|
1103
1055
|
const text = ctx.message?.text;
|
|
1104
1056
|
if (!text) return next();
|
|
1105
|
-
|
|
1106
1057
|
// Extract command from text using the testable filter function
|
|
1107
1058
|
// Note: We pass null for botUsername here and check it separately with ctx.me
|
|
1108
1059
|
// which is set by Telegraf after bot initialization
|
|
@@ -1116,7 +1067,6 @@ bot.on('message', async (ctx, next) => {
|
|
|
1116
1067
|
return next(); // Command is for a different bot or we can't verify
|
|
1117
1068
|
}
|
|
1118
1069
|
}
|
|
1119
|
-
|
|
1120
1070
|
// /subscribe + /unsubscribe (#1688) are intentionally not in the text fallback ā Telegraf's bot.command() is sufficient.
|
|
1121
1071
|
const solveHandlers = Object.fromEntries(SOLVE_COMMAND_NAMES.map(command => [command, handleSolveCommand]));
|
|
1122
1072
|
const taskHandlers = Object.fromEntries(TASK_COMMAND_NAMES.map(command => [command, handleTaskCommand]));
|
|
@@ -1125,13 +1075,11 @@ bot.on('message', async (ctx, next) => {
|
|
|
1125
1075
|
|
|
1126
1076
|
const handler = handlers[extracted.command];
|
|
1127
1077
|
if (!handler) return next();
|
|
1128
|
-
|
|
1129
1078
|
// Log that fallback was triggered - this indicates bot.command() entity matching failed
|
|
1130
1079
|
console.warn(`[WARNING] Command /${extracted.command} matched by text fallback, not by entity-based bot.command(). ` + `Entities: ${JSON.stringify(ctx.message.entities || [])}. ` + `User: ${ctx.from?.username || ctx.from?.id}. ` + `This may indicate a Telegram client entity issue (issue #1207).`);
|
|
1131
1080
|
|
|
1132
1081
|
await handler(ctx);
|
|
1133
1082
|
});
|
|
1134
|
-
|
|
1135
1083
|
// Add global error handler for uncaught errors in middleware
|
|
1136
1084
|
bot.catch((error, ctx) => {
|
|
1137
1085
|
console.error('Unhandled error while processing update', ctx.update.update_id);
|
|
@@ -1162,14 +1110,12 @@ bot.catch((error, ctx) => {
|
|
|
1162
1110
|
username: ctx.from?.username,
|
|
1163
1111
|
},
|
|
1164
1112
|
});
|
|
1165
|
-
|
|
1166
1113
|
// Try to notify the user about the error with more details
|
|
1167
1114
|
if (ctx?.reply) {
|
|
1168
1115
|
const isTelegramParsingError = isTelegramFormattingError(error);
|
|
1169
1116
|
const isTelegramTextLimitError = isTelegramMessageTooLongError(error);
|
|
1170
1117
|
|
|
1171
1118
|
let errorMessage;
|
|
1172
|
-
|
|
1173
1119
|
if (isTelegramParsingError || isTelegramTextLimitError) {
|
|
1174
1120
|
// Issue #1460: Log detailed context for root cause analysis (always logged, not just in verbose mode)
|
|
1175
1121
|
const userInfo = ctx.from ? { id: ctx.from.id, username: ctx.from.username, first_name: ctx.from.first_name, last_name: ctx.from.last_name } : 'unknown';
|
|
@@ -1216,7 +1162,6 @@ bot.catch((error, ctx) => {
|
|
|
1216
1162
|
});
|
|
1217
1163
|
}
|
|
1218
1164
|
});
|
|
1219
|
-
|
|
1220
1165
|
// Track shutdown state to prevent startup messages after shutdown
|
|
1221
1166
|
let isShuttingDown = false;
|
|
1222
1167
|
|
|
@@ -1238,12 +1183,12 @@ if (VERBOSE) {
|
|
|
1238
1183
|
console.log('[VERBOSE] Bot start time (Unix):', BOT_START_TIME);
|
|
1239
1184
|
console.log('[VERBOSE] Bot start time (ISO):', new Date(BOT_START_TIME * 1000).toISOString());
|
|
1240
1185
|
}
|
|
1241
|
-
|
|
1242
1186
|
// Launch bot with retry logic (issue #1240: handle 409 Conflict with exponential backoff)
|
|
1243
1187
|
// The launcher handles deleteWebhook + bot.launch() with retry on transient errors.
|
|
1244
1188
|
// Non-retryable errors (401 Unauthorized) cause immediate exit.
|
|
1245
1189
|
const launchAbortController = new AbortController();
|
|
1246
1190
|
let sessionMonitoringTimer = null;
|
|
1191
|
+
let formalAiMaintenance = null;
|
|
1247
1192
|
let launchAnnouncementShown = false;
|
|
1248
1193
|
|
|
1249
1194
|
function startSessionMonitoringOnce() {
|
|
@@ -1252,12 +1197,24 @@ function startSessionMonitoringOnce() {
|
|
|
1252
1197
|
// working session when `--on-session-kill=resume` is in effect.
|
|
1253
1198
|
sessionMonitoringTimer = startSessionMonitoring(bot, VERBOSE, 30000, { isolationRunner });
|
|
1254
1199
|
}
|
|
1200
|
+
// Issue #2146 (PR #2147 review): stop the Formal AI sidecar once no Formal AI
|
|
1201
|
+
// task holds a lease, then ā while the host is idle ā update its image (with a
|
|
1202
|
+
// non-destructive memory migration) and refresh the agentic CLIs. Every step is
|
|
1203
|
+
// best-effort and never blocks the bot.
|
|
1204
|
+
function startFormalAiMaintenanceOnce() {
|
|
1205
|
+
if (formalAiMaintenance) return;
|
|
1206
|
+
formalAiMaintenance = startFormalAiMaintenance({
|
|
1207
|
+
verbose: VERBOSE,
|
|
1208
|
+
log: async message => {
|
|
1209
|
+
console.log(message);
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1255
1213
|
|
|
1256
1214
|
// Issue #1927 (requirements #3/#4): a periodic timestamped heartbeat so the "last
|
|
1257
1215
|
// time the bot was alive" is always discoverable from the log. The heartbeat
|
|
1258
1216
|
// logic lives in bot-lifecycle.lib.mjs so it can be unit tested.
|
|
1259
1217
|
const heartbeat = createHeartbeat({ logger: botLogger, getActiveSessionCount });
|
|
1260
|
-
|
|
1261
1218
|
async function onBotLaunched() {
|
|
1262
1219
|
if (isShuttingDown || launchAnnouncementShown) return;
|
|
1263
1220
|
launchAnnouncementShown = true;
|
|
@@ -1265,7 +1222,6 @@ async function onBotLaunched() {
|
|
|
1265
1222
|
console.log('ā
SwarmMindBot is now running!');
|
|
1266
1223
|
console.log('Press Ctrl+C to stop');
|
|
1267
1224
|
botLogger.event('bot_launched', { pid: process.pid, botStartTime: BOT_START_TIME });
|
|
1268
|
-
|
|
1269
1225
|
// Issue #1927 (requirements #2/#4): after a restart, reload sessions that were
|
|
1270
1226
|
// still being tracked when the previous process died and re-register them so
|
|
1271
1227
|
// the monitor resumes watching ā and finally reports any that were killed while
|
|
@@ -1274,8 +1230,8 @@ async function onBotLaunched() {
|
|
|
1274
1230
|
await resumeSessionsOnLaunch({ resumeTrackedSessions, botStartTime: BOT_START_TIME, verbose: VERBOSE, logger: botLogger });
|
|
1275
1231
|
|
|
1276
1232
|
startSessionMonitoringOnce();
|
|
1233
|
+
startFormalAiMaintenanceOnce();
|
|
1277
1234
|
heartbeat.start();
|
|
1278
|
-
|
|
1279
1235
|
if (VERBOSE) {
|
|
1280
1236
|
console.log('[VERBOSE] Bot launched successfully');
|
|
1281
1237
|
console.log('[VERBOSE] Polling is active, waiting for messages...');
|
|
@@ -1284,7 +1240,6 @@ async function onBotLaunched() {
|
|
|
1284
1240
|
try {
|
|
1285
1241
|
const botInfo = await bot.telegram.getMe();
|
|
1286
1242
|
const webhookInfo = await bot.telegram.getWebhookInfo();
|
|
1287
|
-
|
|
1288
1243
|
console.log('[VERBOSE] Bot info:');
|
|
1289
1244
|
console.log('[VERBOSE] Username: @' + botInfo.username);
|
|
1290
1245
|
console.log('[VERBOSE] Bot ID:', botInfo.id);
|
|
@@ -1309,7 +1264,6 @@ async function onBotLaunched() {
|
|
|
1309
1264
|
} catch (err) {
|
|
1310
1265
|
console.log('[VERBOSE] Could not fetch bot info:', err.message);
|
|
1311
1266
|
}
|
|
1312
|
-
|
|
1313
1267
|
console.log('[VERBOSE] Send a message to the bot to test message reception');
|
|
1314
1268
|
}
|
|
1315
1269
|
}
|
|
@@ -1318,7 +1272,6 @@ async function onBotLaunched() {
|
|
|
1318
1272
|
// session map is empty until commands are received, but bot.launch() may stay
|
|
1319
1273
|
// pending while polling is active.
|
|
1320
1274
|
startSessionMonitoringOnce();
|
|
1321
|
-
|
|
1322
1275
|
launchBotWithRetry(
|
|
1323
1276
|
bot,
|
|
1324
1277
|
{
|
|
@@ -1355,7 +1308,6 @@ const stopSolveQueue = () => {
|
|
|
1355
1308
|
/* ignore errors during shutdown */
|
|
1356
1309
|
}
|
|
1357
1310
|
};
|
|
1358
|
-
|
|
1359
1311
|
// Issue #1927: record the shutdown (with a timestamp) so the log shows the bot
|
|
1360
1312
|
// stopped cleanly ā the ABSENCE of this line before the next startup is how a
|
|
1361
1313
|
// later analysis tells an orderly stop apart from a hard kill. The handler lives
|
|
@@ -1372,6 +1324,7 @@ const handleShutdownSignal = createShutdownHandler({
|
|
|
1372
1324
|
cleanup: () => {
|
|
1373
1325
|
launchAbortController.abort();
|
|
1374
1326
|
if (sessionMonitoringTimer) clearInterval(sessionMonitoringTimer);
|
|
1327
|
+
formalAiMaintenance?.stop();
|
|
1375
1328
|
heartbeat.stop();
|
|
1376
1329
|
stopSolveQueue();
|
|
1377
1330
|
},
|
|
@@ -1381,7 +1334,6 @@ process.once('SIGINT', () => {
|
|
|
1381
1334
|
console.log('\nš Received SIGINT (Ctrl+C), stopping bot...');
|
|
1382
1335
|
handleShutdownSignal('SIGINT');
|
|
1383
1336
|
});
|
|
1384
|
-
|
|
1385
1337
|
process.once('SIGTERM', () => {
|
|
1386
1338
|
console.log('\nš Received SIGTERM, stopping bot... (Check system logs: journalctl -u <service> or dmesg)');
|
|
1387
1339
|
handleShutdownSignal('SIGTERM');
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { buildUserMention } from './buildUserMention.lib.mjs';
|
|
11
11
|
import { validateModelName } from './models/index.mjs';
|
|
12
12
|
import { parseFixRepository } from './fix.ci-cd.lib.mjs';
|
|
13
|
+
import { getModelFromArgs } from './model-args.lib.mjs';
|
|
13
14
|
import { escapeMarkdown } from './telegram-markdown.lib.mjs';
|
|
14
15
|
import { extractIsolationFromArgs, isValidPerCommandIsolation } from './telegram-isolation.lib.mjs';
|
|
15
16
|
import { mergeArgsWithOverrides } from './args-overrides.lib.mjs';
|
|
@@ -47,14 +48,6 @@ export function getFixToolFromArgs(args) {
|
|
|
47
48
|
return 'claude';
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function getModelFromArgs(args) {
|
|
51
|
-
for (let i = 0; i < args.length; i++) {
|
|
52
|
-
if ((args[i] === '--model' || args[i] === '-m') && i + 1 < args.length) return args[i + 1];
|
|
53
|
-
if (args[i].startsWith('--model=')) return args[i].substring('--model='.length);
|
|
54
|
-
}
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
51
|
function validateFixModel(args) {
|
|
59
52
|
const model = getModelFromArgs(args);
|
|
60
53
|
if (!model) return null;
|