@link-assistant/hive-mind 2.13.1 โ 2.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/buildUserMention.lib.mjs +30 -3
- package/src/github-url-parser.lib.mjs +26 -1
- package/src/session-monitor.lib.mjs +3 -2
- package/src/telegram-accept-invitations.lib.mjs +5 -3
- package/src/telegram-bot.mjs +18 -9
- package/src/telegram-command-execution.lib.mjs +2 -1
- package/src/telegram-context-safety.lib.mjs +70 -0
- package/src/telegram-fix-command.lib.mjs +68 -4
- package/src/telegram-language-command.lib.mjs +4 -3
- package/src/telegram-log-command.lib.mjs +14 -12
- package/src/telegram-markdown-validator.lib.mjs +192 -0
- package/src/telegram-merge-command.lib.mjs +18 -16
- package/src/telegram-message-filters.lib.mjs +1 -1
- package/src/telegram-safe-reply.lib.mjs +290 -21
- package/src/telegram-solve-queue-command.lib.mjs +2 -1
- package/src/telegram-solve-queue.lib.mjs +16 -7
- package/src/telegram-start-stop-command.lib.mjs +38 -27
- package/src/telegram-subscribers.lib.mjs +6 -4
- package/src/telegram-terminal-watch-command.lib.mjs +8 -7
- package/src/telegram-tokens-command.lib.mjs +2 -1
- package/src/telegram-top-command.lib.mjs +8 -9
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
import { extractSessionIdFromText } from './telegram-log-command.lib.mjs';
|
|
32
32
|
import { parseGitHubUrl } from './github.lib.mjs';
|
|
33
33
|
import { cleanNonPrintableChars } from './telegram-markdown.lib.mjs';
|
|
34
|
+
// Issue #2166: every reply/edit in this module goes through the one send funnel
|
|
35
|
+
// (validated, logged, plain-text fallback) even when a caller does not inject it.
|
|
36
|
+
import { safeReply as defaultSafeReply, safeEditMessageText as defaultSafeEditMessageText } from './telegram-safe-reply.lib.mjs';
|
|
34
37
|
|
|
35
38
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
36
39
|
|
|
@@ -146,7 +149,9 @@ function findFirstIssueOrPullUrl(text) {
|
|
|
146
149
|
if (!word) continue;
|
|
147
150
|
const parsed = parseGitHubUrl(word);
|
|
148
151
|
if (parsed.valid && (parsed.type === 'issue' || parsed.type === 'pull')) {
|
|
149
|
-
|
|
152
|
+
// Issue #2166: match on what the bot interprets, not on the fragment the
|
|
153
|
+
// user happened to copy along with the link.
|
|
154
|
+
return parsed.canonical || parsed.normalized;
|
|
150
155
|
}
|
|
151
156
|
}
|
|
152
157
|
return null;
|
|
@@ -203,14 +208,17 @@ export function extractStopTarget(text, repliedTo) {
|
|
|
203
208
|
* @returns {Promise<boolean>} true when the card was edited
|
|
204
209
|
* @see https://github.com/link-assistant/hive-mind/issues/1783
|
|
205
210
|
*/
|
|
206
|
-
export async function updateQueueCardForCancellation(item, url, tool, stopperName) {
|
|
211
|
+
export async function updateQueueCardForCancellation(item, url, tool, stopperName, { safeEditMessageText = defaultSafeEditMessageText, verbose = false } = {}) {
|
|
207
212
|
if (!item || !item.messageInfo || !item.ctx) return false;
|
|
208
213
|
const toolSuffix = tool ? ` from \`${tool}\` queue` : '';
|
|
209
214
|
const stopperSuffix = stopperName ? ` by ${stopperName}` : '';
|
|
210
215
|
const text = `๐ *Cancelled*\n\n${url}\n\nRemoved${toolSuffix}${stopperSuffix} via /stop.`;
|
|
211
216
|
try {
|
|
212
217
|
const { chatId, messageId } = item.messageInfo;
|
|
213
|
-
|
|
218
|
+
// Issue #2166: go through the shared safe funnel when the caller provides
|
|
219
|
+
// it, so a card whose URL contains Markdown-significant characters is
|
|
220
|
+
// still updated (as plain text) instead of failing silently.
|
|
221
|
+
await safeEditMessageText(item.ctx.telegram, chatId, messageId, undefined, text, { verbose });
|
|
214
222
|
// Match the consumer's contract: once a card reaches a terminal state we
|
|
215
223
|
// forget the message coordinates so nothing else tries to edit it.
|
|
216
224
|
item.messageInfo = null;
|
|
@@ -270,7 +278,10 @@ export function isStopTargetRequester({ userId, queueItem = null, sessionInfo =
|
|
|
270
278
|
* See https://github.com/link-assistant/hive-mind/issues/1871.
|
|
271
279
|
*/
|
|
272
280
|
export function registerStartStopCommands(bot, options) {
|
|
273
|
-
|
|
281
|
+
// Issue #2166: every reply goes through the shared safe-send funnel (pre-send
|
|
282
|
+
// validation, audit log, plain-text fallback). The defaults keep the module
|
|
283
|
+
// usable in isolation (tests) without a bot wired up.
|
|
284
|
+
const { VERBOSE = false, isOldMessage, isForwarded, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, getSolveQueue, safeReply = defaultSafeReply, safeEditMessageText = defaultSafeEditMessageText } = options;
|
|
274
285
|
const stopIsolatedSessionImpl = options.stopIsolatedSession || (async (...args) => (await import('./isolation-runner.lib.mjs')).stopIsolatedSession(...args));
|
|
275
286
|
// Issue #1783: look the UUID up in the session monitor so /stop can let the
|
|
276
287
|
// user who started the task stop it (mirrors /terminal_watch from PR #1779).
|
|
@@ -344,25 +355,25 @@ export function registerStartStopCommands(bot, options) {
|
|
|
344
355
|
if (!isGroupChat(ctx)) {
|
|
345
356
|
if (opts.allowPrivate) return { valid: false, isPrivate: true };
|
|
346
357
|
VERBOSE && console.log(`[VERBOSE] ${cmdName} ignored: not a group chat`);
|
|
347
|
-
await ctx
|
|
358
|
+
await safeReply(ctx, `โ The ${cmdName} command only works in group chats.`, { reply_to_message_id: ctx.message.message_id });
|
|
348
359
|
return { valid: false };
|
|
349
360
|
}
|
|
350
361
|
const chatId = ctx.chat.id;
|
|
351
362
|
if (!isChatAuthorized(chatId)) {
|
|
352
363
|
VERBOSE && console.log(`[VERBOSE] ${cmdName} ignored: chat not authorized`);
|
|
353
|
-
await ctx
|
|
364
|
+
await safeReply(ctx, `โ This chat (ID: ${chatId}) is not authorized to use this bot.`, { reply_to_message_id: ctx.message.message_id });
|
|
354
365
|
return { valid: false };
|
|
355
366
|
}
|
|
356
367
|
try {
|
|
357
368
|
const chatMember = await ctx.telegram.getChatMember(chatId, ctx.from.id);
|
|
358
369
|
if (chatMember.status !== 'creator') {
|
|
359
370
|
VERBOSE && console.log(`[VERBOSE] ${cmdName} ignored: user is not chat owner`);
|
|
360
|
-
await ctx
|
|
371
|
+
await safeReply(ctx, 'โ This command is only available to the chat owner.', { reply_to_message_id: ctx.message.message_id });
|
|
361
372
|
return { valid: false };
|
|
362
373
|
}
|
|
363
374
|
} catch (error) {
|
|
364
375
|
console.error('[ERROR] Failed to check chat member status:', error);
|
|
365
|
-
await ctx
|
|
376
|
+
await safeReply(ctx, 'โ Failed to verify permissions.', { reply_to_message_id: ctx.message.message_id });
|
|
366
377
|
return { valid: false };
|
|
367
378
|
}
|
|
368
379
|
VERBOSE && console.log(`[VERBOSE] ${cmdName} passed all checks`);
|
|
@@ -395,13 +406,13 @@ export function registerStartStopCommands(bot, options) {
|
|
|
395
406
|
const chatType = ctx.chat?.type;
|
|
396
407
|
if (chatType === 'private') return true;
|
|
397
408
|
if (!isGroupChat(ctx)) {
|
|
398
|
-
await ctx
|
|
409
|
+
await safeReply(ctx, 'โ The /stop command only works in group chats or private chats with the bot.', { reply_to_message_id: message.message_id });
|
|
399
410
|
return false;
|
|
400
411
|
}
|
|
401
412
|
if (!isChatAuthorized(chatId)) {
|
|
402
413
|
if (!isTopicAuthorized || !isTopicAuthorized(ctx)) {
|
|
403
414
|
const errMsg = buildAuthErrorMessage ? buildAuthErrorMessage(ctx) : `โ This chat (ID: ${chatId}) is not authorized to use this bot.`;
|
|
404
|
-
await ctx
|
|
415
|
+
await safeReply(ctx, errMsg, { reply_to_message_id: message.message_id });
|
|
405
416
|
return false;
|
|
406
417
|
}
|
|
407
418
|
}
|
|
@@ -415,12 +426,12 @@ export function registerStartStopCommands(bot, options) {
|
|
|
415
426
|
const member = await ctx.telegram.getChatMember(chatId, ctx.from.id);
|
|
416
427
|
if (!member || member.status !== 'creator') {
|
|
417
428
|
VERBOSE && console.log(`[VERBOSE] /stop <${label}> ignored: user is not chat owner or task requester`);
|
|
418
|
-
await ctx
|
|
429
|
+
await safeReply(ctx, `โ /stop <${label}> is only available to the chat owner or the user who started this task.`, { reply_to_message_id: message.message_id });
|
|
419
430
|
return false;
|
|
420
431
|
}
|
|
421
432
|
} catch (error) {
|
|
422
433
|
console.error(`[ERROR] /stop <${label}>: getChatMember failed:`, error);
|
|
423
|
-
await ctx
|
|
434
|
+
await safeReply(ctx, 'โ Failed to verify permissions for /stop.', { reply_to_message_id: message.message_id });
|
|
424
435
|
return false;
|
|
425
436
|
}
|
|
426
437
|
return true;
|
|
@@ -438,7 +449,7 @@ export function registerStartStopCommands(bot, options) {
|
|
|
438
449
|
*/
|
|
439
450
|
async function runStopIsolatedSessionFlow(ctx, sessionId) {
|
|
440
451
|
const message = ctx.message;
|
|
441
|
-
const ack = await ctx
|
|
452
|
+
const ack = await safeReply(ctx, `โน๏ธ Asking session \`${sessionId}\` to stop (sending CTRL+C via \`$ --stop\`)โฆ`, {
|
|
442
453
|
parse_mode: 'Markdown',
|
|
443
454
|
reply_to_message_id: message.message_id,
|
|
444
455
|
});
|
|
@@ -481,10 +492,10 @@ export function registerStartStopCommands(bot, options) {
|
|
|
481
492
|
}
|
|
482
493
|
|
|
483
494
|
try {
|
|
484
|
-
await ctx.telegram
|
|
495
|
+
await safeEditMessageText(ctx.telegram, ack.chat.id, ack.message_id, undefined, lines.join('\n'), { verbose: VERBOSE });
|
|
485
496
|
} catch (error) {
|
|
486
497
|
console.error('[ERROR] /stop: editMessageText failed, falling back to reply:', error);
|
|
487
|
-
await ctx
|
|
498
|
+
await safeReply(ctx, lines.join('\n'), { parse_mode: 'Markdown', reply_to_message_id: message.message_id });
|
|
488
499
|
}
|
|
489
500
|
}
|
|
490
501
|
|
|
@@ -621,13 +632,13 @@ export function registerStartStopCommands(bot, options) {
|
|
|
621
632
|
// running-but-non-stoppable (non-isolation) session, say so; otherwise
|
|
622
633
|
// fall back to the UUID hint.
|
|
623
634
|
if (runningSession) {
|
|
624
|
-
await ctx
|
|
635
|
+
await safeReply(ctx, `โ ๏ธ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time run it with the default isolation backend or pass \`--isolation docker\` to make this task interruptible via \`/stop\`.`, {
|
|
625
636
|
parse_mode: 'Markdown',
|
|
626
637
|
reply_to_message_id: message.message_id,
|
|
627
638
|
});
|
|
628
639
|
return;
|
|
629
640
|
}
|
|
630
|
-
await ctx
|
|
641
|
+
await safeReply(ctx, `โน๏ธ Cannot look up tasks by URL right now (the bot has no solve queue available in this context).\n\nIf you have the session UUID, you can use \`/stop <UUID>\` instead.`, {
|
|
631
642
|
parse_mode: 'Markdown',
|
|
632
643
|
reply_to_message_id: message.message_id,
|
|
633
644
|
});
|
|
@@ -639,13 +650,13 @@ export function registerStartStopCommands(bot, options) {
|
|
|
639
650
|
// have forwarded CTRL+C above). If it tracked a non-isolation session,
|
|
640
651
|
// explain why it can't be stopped; otherwise report not found.
|
|
641
652
|
if (runningSession) {
|
|
642
|
-
await ctx
|
|
653
|
+
await safeReply(ctx, `โ ๏ธ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time run it with the default isolation backend or pass \`--isolation docker\` to make this task interruptible via \`/stop\`.`, {
|
|
643
654
|
parse_mode: 'Markdown',
|
|
644
655
|
reply_to_message_id: message.message_id,
|
|
645
656
|
});
|
|
646
657
|
return;
|
|
647
658
|
}
|
|
648
|
-
await ctx
|
|
659
|
+
await safeReply(ctx, `โน๏ธ No queued or running task found for ${url}.\n\nIf the task is running with an isolation backend, try \`/stop <UUID>\` (the UUID is shown in the bot's session-id message).`, {
|
|
649
660
|
parse_mode: 'Markdown',
|
|
650
661
|
reply_to_message_id: message.message_id,
|
|
651
662
|
});
|
|
@@ -661,9 +672,9 @@ export function registerStartStopCommands(bot, options) {
|
|
|
661
672
|
// coordinates on the item itself when the card was first posted โ
|
|
662
673
|
// see telegram-bot.mjs where `item.messageInfo` is wired up.
|
|
663
674
|
const stopperName = ctx.from?.username ? `@${ctx.from.username}` : ctx.from?.first_name || `user ${ctx.from?.id}`;
|
|
664
|
-
await updateQueueCardForCancellation(lookup.item, url, lookup.tool, stopperName);
|
|
675
|
+
await updateQueueCardForCancellation(lookup.item, url, lookup.tool, stopperName, { safeEditMessageText, verbose: VERBOSE });
|
|
665
676
|
|
|
666
|
-
await ctx
|
|
677
|
+
await safeReply(ctx, `๐ Removed queued task for ${url}${toolLabel}.`, {
|
|
667
678
|
parse_mode: 'Markdown',
|
|
668
679
|
reply_to_message_id: message.message_id,
|
|
669
680
|
});
|
|
@@ -679,7 +690,7 @@ export function registerStartStopCommands(bot, options) {
|
|
|
679
690
|
// running-not-isolated: a started, non-isolated screen session. We
|
|
680
691
|
// could shell out to `screen -X -S <name> stuff $'\003'`, but that's
|
|
681
692
|
// brittle and out of scope for #1780. Tell the user how to recover.
|
|
682
|
-
await ctx
|
|
693
|
+
await safeReply(ctx, `โ ๏ธ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time run it with the default isolation backend or pass \`--isolation docker\` to make this task interruptible via \`/stop\`.`, {
|
|
683
694
|
parse_mode: 'Markdown',
|
|
684
695
|
reply_to_message_id: message.message_id,
|
|
685
696
|
});
|
|
@@ -707,7 +718,7 @@ export function registerStartStopCommands(bot, options) {
|
|
|
707
718
|
alreadyStoppedMsg += `\nReason: ${stopInfo.reason}`;
|
|
708
719
|
}
|
|
709
720
|
alreadyStoppedMsg += '\n\nUse /start to resume accepting tasks.';
|
|
710
|
-
await ctx
|
|
721
|
+
await safeReply(ctx, alreadyStoppedMsg, {
|
|
711
722
|
reply_to_message_id: ctx.message.message_id,
|
|
712
723
|
});
|
|
713
724
|
return;
|
|
@@ -739,7 +750,7 @@ export function registerStartStopCommands(bot, options) {
|
|
|
739
750
|
}
|
|
740
751
|
stopMessage += '*Disabled commands:*\n' + 'โข /solve - No new issues will be accepted\n' + 'โข /hive - No new hive commands will be accepted\n' + 'โข /merge - No new merge operations will be accepted\n\n' + '*Still available:*\n' + 'โข /help - Show help\n' + 'โข /limits - Show usage limits\n' + 'โข /version - Show version info\n' + 'โข /start - Resume accepting tasks (owner only)\n\n' + '๐ก Any tasks already in queue will continue to process.';
|
|
741
752
|
|
|
742
|
-
await ctx
|
|
753
|
+
await safeReply(ctx, stopMessage, {
|
|
743
754
|
parse_mode: 'Markdown',
|
|
744
755
|
reply_to_message_id: ctx.message.message_id,
|
|
745
756
|
});
|
|
@@ -755,7 +766,7 @@ export function registerStartStopCommands(bot, options) {
|
|
|
755
766
|
// In private chats, show a welcome message instead
|
|
756
767
|
if (check.isPrivate) {
|
|
757
768
|
VERBOSE && console.log('[VERBOSE] /start in private chat: showing welcome');
|
|
758
|
-
await ctx
|
|
769
|
+
await safeReply(ctx, '๐ *Welcome to SwarmMindBot!*\n\n' + 'This bot helps solve GitHub issues using AI.\n\n' + 'To use this bot:\n' + '1. Add me to a group chat\n' + '2. Make me an admin\n' + '3. Use /solve to solve GitHub issues\n\n' + 'Use /help in a group chat for more information.', { parse_mode: 'Markdown' });
|
|
759
770
|
}
|
|
760
771
|
return;
|
|
761
772
|
}
|
|
@@ -763,7 +774,7 @@ export function registerStartStopCommands(bot, options) {
|
|
|
763
774
|
|
|
764
775
|
// Check if already running (not stopped)
|
|
765
776
|
if (!isChatStopped(chatId)) {
|
|
766
|
-
await ctx
|
|
777
|
+
await safeReply(ctx, 'โน๏ธ Bot is already accepting tasks in this chat.\n\nUse /help to see available commands.', {
|
|
767
778
|
reply_to_message_id: ctx.message.message_id,
|
|
768
779
|
});
|
|
769
780
|
return;
|
|
@@ -791,7 +802,7 @@ export function registerStartStopCommands(bot, options) {
|
|
|
791
802
|
}
|
|
792
803
|
}
|
|
793
804
|
|
|
794
|
-
await ctx
|
|
805
|
+
await safeReply(ctx, 'โ
*Bot Started*\n\n' + 'This bot is now accepting tasks in this chat.\n\n' + (durationStr ? `Bot was stopped for ${durationStr}.\n\n` : '') + 'Use /help to see available commands.', {
|
|
795
806
|
parse_mode: 'Markdown',
|
|
796
807
|
reply_to_message_id: ctx.message.message_id,
|
|
797
808
|
});
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* @see https://github.com/link-assistant/hive-mind/issues/1688
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { safeReply } from './telegram-safe-reply.lib.mjs';
|
|
18
|
+
|
|
17
19
|
// Map<userId, { username, firstName, subscribedAt, sourceChatId }>
|
|
18
20
|
const subscribers = new Map();
|
|
19
21
|
|
|
@@ -196,9 +198,9 @@ export function registerSubscribeCommands(bot, options = {}) {
|
|
|
196
198
|
message = 'โน๏ธ You are already subscribed.\n\nUse /unsubscribe to stop receiving private notifications when /solve commands finish.';
|
|
197
199
|
}
|
|
198
200
|
|
|
199
|
-
await ctx
|
|
200
|
-
parse_mode: 'Markdown',
|
|
201
|
+
await safeReply(ctx, message, {
|
|
201
202
|
reply_to_message_id: ctx.message?.message_id,
|
|
203
|
+
verbose: VERBOSE,
|
|
202
204
|
});
|
|
203
205
|
|
|
204
206
|
VERBOSE && console.log(`[VERBOSE] Subscriber ${userId} (${ctx.from.username || ctx.from.first_name || 'unknown'}) added (new=${wasNew}); total=${getSubscriberCount()}`);
|
|
@@ -210,9 +212,9 @@ export function registerSubscribeCommands(bot, options = {}) {
|
|
|
210
212
|
const userId = ctx.from.id;
|
|
211
213
|
const wasRemoved = removeSubscriber(userId);
|
|
212
214
|
|
|
213
|
-
await ctx
|
|
214
|
-
parse_mode: 'Markdown',
|
|
215
|
+
await safeReply(ctx, wasRemoved ? UNSUBSCRIBE_CONFIRMATION : NOT_SUBSCRIBED_MESSAGE, {
|
|
215
216
|
reply_to_message_id: ctx.message?.message_id,
|
|
217
|
+
verbose: VERBOSE,
|
|
216
218
|
});
|
|
217
219
|
|
|
218
220
|
VERBOSE && console.log(`[VERBOSE] Subscriber ${userId} (${ctx.from.username || ctx.from.first_name || 'unknown'}) removed=${wasRemoved}; total=${getSubscriberCount()}`);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import { extractSessionIdFromText, decideLogDestination, resolveLogPath } from './telegram-log-command.lib.mjs';
|
|
10
10
|
import { parseSessionExitFooter } from './isolation-runner.lib.mjs';
|
|
11
|
+
import { safeReply, safeSendMessage, safeEditMessageText } from './telegram-safe-reply.lib.mjs';
|
|
11
12
|
import { classifyExitStatus, isFailureSessionStatus } from './session-status.lib.mjs';
|
|
12
13
|
|
|
13
14
|
const DEFAULT_WIDTH = 120;
|
|
@@ -248,7 +249,7 @@ export function watchTerminalLogSession({ bot, chatId, messageId, sessionId, log
|
|
|
248
249
|
const message = formatTerminalWatchMessage({ sessionId, statusResult, logText, options, updateCount, completed, repoDescription });
|
|
249
250
|
const shouldEdit = !lastMessage || snapshotChanged || (completed && message !== lastMessage);
|
|
250
251
|
if (shouldEdit && message !== lastMessage) {
|
|
251
|
-
await bot.telegram
|
|
252
|
+
await safeEditMessageText(bot.telegram, chatId, messageId, undefined, message, { verbose });
|
|
252
253
|
lastMessage = message;
|
|
253
254
|
}
|
|
254
255
|
lastSnapshot = snapshot;
|
|
@@ -281,9 +282,9 @@ function buildUsage() {
|
|
|
281
282
|
|
|
282
283
|
async function createWatchMessage({ ctx, targetChatId, replyToMessageId, text }) {
|
|
283
284
|
if (targetChatId === ctx.chat.id) {
|
|
284
|
-
return await ctx
|
|
285
|
+
return await safeReply(ctx, text, { reply_to_message_id: replyToMessageId });
|
|
285
286
|
}
|
|
286
|
-
return await ctx.telegram
|
|
287
|
+
return await safeSendMessage(ctx.telegram, targetChatId, text, replyToMessageId ? { reply_to_message_id: replyToMessageId } : {});
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
async function forwardOrCopyToDm(ctx, sourceMessage) {
|
|
@@ -324,7 +325,7 @@ async function startWatchFromResolvedSession({ bot, ctx, sessionId, statusResult
|
|
|
324
325
|
watchTerminalLogSession({ bot, chatId: targetChatId, messageId: watchMessage.message_id, sessionId, logPath, querySessionStatus, isTerminalSessionStatus, options: watchOptions, repoDescription, verbose, initialStatusResult: statusResult, initialLogText, initialMessage: initialText });
|
|
325
326
|
|
|
326
327
|
if (!auto && decision.destination === 'dm' && ctx.chat.type !== 'private') {
|
|
327
|
-
await ctx
|
|
328
|
+
await safeReply(ctx, `๐ฌ Started terminal watch for \`${sessionId}\` in your direct messages.`, { reply_to_message_id: ctx.message.message_id, verbose });
|
|
328
329
|
}
|
|
329
330
|
return { started: true, messageId: watchMessage.message_id, sessionInfo };
|
|
330
331
|
}
|
|
@@ -371,13 +372,13 @@ export async function registerTerminalWatchCommand(bot, options) {
|
|
|
371
372
|
|
|
372
373
|
const parsedArgs = parseTerminalWatchArgs(message.text || '');
|
|
373
374
|
if (parsedArgs.errors.length > 0) {
|
|
374
|
-
await ctx
|
|
375
|
+
await safeReply(ctx, `โ Invalid /terminal_watch options:\n${parsedArgs.errors.map(e => `โข ${e}`).join('\n')}\n\n${buildUsage()}`, { reply_to_message_id: message.message_id, verbose: VERBOSE });
|
|
375
376
|
return;
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
const sessionId = parsedArgs.sessionId || extractSessionIdFromText(message.reply_to_message?.text || message.reply_to_message?.caption || '');
|
|
379
380
|
if (!sessionId) {
|
|
380
|
-
await ctx
|
|
381
|
+
await safeReply(ctx, `โ /terminal_watch requires a session id.\n\n${buildUsage()}`, { reply_to_message_id: message.message_id, verbose: VERBOSE });
|
|
381
382
|
return;
|
|
382
383
|
}
|
|
383
384
|
|
|
@@ -417,7 +418,7 @@ export async function registerTerminalWatchCommand(bot, options) {
|
|
|
417
418
|
}
|
|
418
419
|
|
|
419
420
|
if (!statusResult?.exists) {
|
|
420
|
-
await ctx
|
|
421
|
+
await safeReply(ctx, `โ Session \`${sessionId}\` is not known to start-command.`, { reply_to_message_id: message.message_id, verbose: VERBOSE });
|
|
421
422
|
return;
|
|
422
423
|
}
|
|
423
424
|
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { getAllKnownLocalTokens } from './token-sanitization.lib.mjs';
|
|
26
26
|
import { maskToken } from './lib.mjs';
|
|
27
|
+
import { safeReply } from './telegram-safe-reply.lib.mjs';
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Resolve allowed chat IDs into an array of numeric IDs the user could own.
|
|
@@ -146,7 +147,7 @@ export const registerTokensCommand = (bot, options = {}) => {
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
const message = formatTokenList(tokens);
|
|
149
|
-
await ctx
|
|
150
|
+
await safeReply(ctx, message);
|
|
150
151
|
});
|
|
151
152
|
};
|
|
152
153
|
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { promisify } from 'util';
|
|
18
18
|
import { exec as execCallback } from 'child_process';
|
|
19
|
+
import { safeReply, safeEditMessageText } from './telegram-safe-reply.lib.mjs';
|
|
19
20
|
|
|
20
21
|
const exec = promisify(execCallback);
|
|
21
22
|
|
|
@@ -128,9 +129,9 @@ export function registerTopCommand(bot, options) {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
// Show experimental feature warning
|
|
131
|
-
await ctx
|
|
132
|
-
parse_mode: 'Markdown',
|
|
132
|
+
await safeReply(ctx, '๐งช *EXPERIMENTAL FEATURE*\n\nThis command is experimental and may have issues. Use with caution.', {
|
|
133
133
|
reply_to_message_id: ctx.message.message_id,
|
|
134
|
+
verbose: VERBOSE,
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
// Check if there's already an active top session for this chat
|
|
@@ -188,11 +189,11 @@ export function registerTopCommand(bot, options) {
|
|
|
188
189
|
const firstOutput = await captureTopOutput(chatId);
|
|
189
190
|
if (firstOutput) {
|
|
190
191
|
try {
|
|
191
|
-
await ctx.telegram
|
|
192
|
-
parse_mode: 'Markdown',
|
|
192
|
+
await safeEditMessageText(ctx.telegram, chatId, initialMessage.message_id, undefined, `\`\`\`\n${firstOutput}\n\`\`\``, {
|
|
193
193
|
reply_markup: {
|
|
194
194
|
inline_keyboard: [[{ text: '๐ Stop', callback_data: `stop_top_${chatId}` }]],
|
|
195
195
|
},
|
|
196
|
+
verbose: VERBOSE,
|
|
196
197
|
});
|
|
197
198
|
} catch (error) {
|
|
198
199
|
console.error('[ERROR] Failed to update message:', error);
|
|
@@ -204,11 +205,11 @@ export function registerTopCommand(bot, options) {
|
|
|
204
205
|
const output = await captureTopOutput(chatId);
|
|
205
206
|
if (output) {
|
|
206
207
|
try {
|
|
207
|
-
await ctx.telegram
|
|
208
|
-
parse_mode: 'Markdown',
|
|
208
|
+
await safeEditMessageText(ctx.telegram, chatId, initialMessage.message_id, undefined, `\`\`\`\n${output}\n\`\`\``, {
|
|
209
209
|
reply_markup: {
|
|
210
210
|
inline_keyboard: [[{ text: '๐ Stop', callback_data: `stop_top_${chatId}` }]],
|
|
211
211
|
},
|
|
212
|
+
verbose: VERBOSE,
|
|
212
213
|
});
|
|
213
214
|
} catch (error) {
|
|
214
215
|
// Ignore "message is not modified" errors
|
|
@@ -290,9 +291,7 @@ export function registerTopCommand(bot, options) {
|
|
|
290
291
|
|
|
291
292
|
// Update the message to show it's stopped
|
|
292
293
|
try {
|
|
293
|
-
await ctx.
|
|
294
|
-
parse_mode: 'Markdown',
|
|
295
|
-
});
|
|
294
|
+
await safeEditMessageText(ctx.telegram, chatId, ctx.callbackQuery?.message?.message_id, undefined, '๐ Top session stopped.', { verbose: VERBOSE });
|
|
296
295
|
} catch (error) {
|
|
297
296
|
console.error('[ERROR] Failed to edit message:', error);
|
|
298
297
|
}
|