appback-remoteagent 0.13.0 → 0.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/dist/bot.js CHANGED
@@ -1030,12 +1030,11 @@ function sanitizeLoggedTelegramText(text) {
1030
1030
  async function runWithPendingAnimation(botToken, chatId, task) {
1031
1031
  let typingStopped = false;
1032
1032
  let typingTimer;
1033
- const typingStartedAt = Date.now();
1034
- const pulseTyping = () => {
1033
+ const pulseTyping = async () => {
1035
1034
  if (typingStopped) {
1036
1035
  return;
1037
1036
  }
1038
- void sendTelegramChatAction(botToken, chatId, "typing").catch((error) => {
1037
+ await sendTelegramChatAction(botToken, chatId, "typing").catch((error) => {
1039
1038
  if (isTelegramForbiddenError(error)) {
1040
1039
  console.warn(`[telegram-delivery] chat=${chatId} skipped typing action: ${error instanceof Error ? error.message : String(error)}`);
1041
1040
  typingStopped = true;
@@ -1043,12 +1042,14 @@ async function runWithPendingAnimation(botToken, chatId, task) {
1043
1042
  }
1044
1043
  console.warn(`[telegram-chat-action] chat=${chatId} failed: ${error instanceof Error ? error.message : String(error)}`);
1045
1044
  });
1046
- const elapsedMs = Date.now() - typingStartedAt;
1047
- const nextDelayMs = elapsedMs < 60_000 ? 4_000 : 30_000;
1048
- typingTimer = setTimeout(pulseTyping, nextDelayMs);
1045
+ typingTimer = setTimeout(() => {
1046
+ void pulseTyping();
1047
+ }, config.telegramTypingIntervalMs);
1049
1048
  typingTimer.unref?.();
1050
1049
  };
1051
- pulseTyping();
1050
+ await withTimeout(pulseTyping(), 1500).catch((error) => {
1051
+ console.warn(`[telegram-chat-action] chat=${chatId} initial typing action was not confirmed: ${error instanceof Error ? error.message : String(error)}`);
1052
+ });
1052
1053
  try {
1053
1054
  const helpers = {
1054
1055
  reportProgress: async (chunks, parseMode) => {
@@ -1470,6 +1471,23 @@ function formatProviderTimeoutFinalMessage(message) {
1470
1471
  async function sleep(ms) {
1471
1472
  await new Promise((resolve) => setTimeout(resolve, ms));
1472
1473
  }
1474
+ async function withTimeout(promise, timeoutMs) {
1475
+ let timer;
1476
+ try {
1477
+ return await Promise.race([
1478
+ promise,
1479
+ new Promise((_, reject) => {
1480
+ timer = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs);
1481
+ timer.unref?.();
1482
+ }),
1483
+ ]);
1484
+ }
1485
+ finally {
1486
+ if (timer) {
1487
+ clearTimeout(timer);
1488
+ }
1489
+ }
1490
+ }
1473
1491
  async function ensureOwnerControlAccess(ctx) {
1474
1492
  if (ctx.chat?.type !== "private") {
1475
1493
  throw new Error("This command is available only in private 1:1 chats.");
package/dist/config.js CHANGED
@@ -136,6 +136,7 @@ export const config = {
136
136
  telegramPollingMaxConcurrency: readTimeout("TELEGRAM_POLLING_MAX_CONCURRENCY", 3),
137
137
  telegramOwnerId: readOptional("TELEGRAM_OWNER_ID"),
138
138
  telegramMessageBatchMs: readNonNegativeTimeout("TELEGRAM_MESSAGE_BATCH_MS", 1500),
139
+ telegramTypingIntervalMs: readTimeout("TELEGRAM_TYPING_INTERVAL_MS", 4_000),
139
140
  telegramAutoProgressMaxTurns: readOptionalNonNegativeInteger("TELEGRAM_AUTO_PROGRESS_MAX_TURNS") ?? 6,
140
141
  telegramEmptyResponseRetries: readOptionalNonNegativeInteger("TELEGRAM_EMPTY_RESPONSE_RETRIES") ?? 1,
141
142
  telegramRetryableErrorRetries: readOptionalNonNegativeInteger("TELEGRAM_RETRYABLE_ERROR_RETRIES") ?? 2,
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ async function main() {
60
60
  console.error("Local UI failed to start:", error);
61
61
  });
62
62
  }
63
- const botInfos = config.telegramBotTokens.map((token, index) => buildBotInfo(token, index));
63
+ const botInfos = await Promise.all(config.telegramBotTokens.map((token, index) => resolveBotInfo(token, index)));
64
64
  const bots = config.telegramBotTokens.map((token, index) => createBot(token, bridge, botManagement, botInfos[index]));
65
65
  if (config.telegramCommandMenuEnabled) {
66
66
  for (const bot of bots) {
@@ -416,11 +416,41 @@ class AsyncSemaphore {
416
416
  }
417
417
  }
418
418
  }
419
- function buildBotInfo(token, index) {
419
+ async function resolveBotInfo(token, index) {
420
+ try {
421
+ const { stdout } = await execFileAsync("curl", [
422
+ "-sS",
423
+ "-4",
424
+ "--max-time",
425
+ "10",
426
+ `https://api.telegram.org/bot${token}/getMe`,
427
+ ]);
428
+ const payload = JSON.parse(stdout);
429
+ if (payload.ok && payload.result?.id && payload.result.username) {
430
+ return buildBotInfoFromIdentity(payload.result.id, payload.result.username, payload.result.first_name);
431
+ }
432
+ console.warn(`Telegram getMe failed for bot ${tokenIdLabel(token)}: ${payload.description || "missing bot identity"}`);
433
+ }
434
+ catch (error) {
435
+ console.warn(`Telegram getMe failed for bot ${tokenIdLabel(token)}: ${summarizeTelegramIdentityError(error)}`);
436
+ }
437
+ return buildFallbackBotInfo(token, index);
438
+ }
439
+ function buildBotInfoFromIdentity(id, username, firstName) {
440
+ return {
441
+ id,
442
+ is_bot: true,
443
+ first_name: firstName || username,
444
+ username,
445
+ can_join_groups: false,
446
+ can_read_all_group_messages: false,
447
+ supports_inline_queries: false,
448
+ };
449
+ }
450
+ function buildFallbackBotInfo(token, index) {
420
451
  const id = Number.parseInt(token.split(":", 1)[0] ?? "", 10);
421
- const configuredUsername = config.telegramBotUsernames[index];
422
452
  const fallbackUsername = knownBotUsername(id);
423
- const username = configuredUsername || fallbackUsername || `bot_${Number.isFinite(id) ? id : index + 1}`;
453
+ const username = fallbackUsername || `bot_${Number.isFinite(id) ? id : index + 1}`;
424
454
  return {
425
455
  id: Number.isFinite(id) ? id : index + 1,
426
456
  is_bot: true,
@@ -431,6 +461,20 @@ function buildBotInfo(token, index) {
431
461
  supports_inline_queries: false,
432
462
  };
433
463
  }
464
+ function tokenIdLabel(token) {
465
+ return token.split(":", 1)[0] || "unknown";
466
+ }
467
+ function summarizeTelegramIdentityError(error) {
468
+ if (!(error instanceof Error)) {
469
+ return String(error);
470
+ }
471
+ const code = typeof error === "object" && error !== null && "code" in error
472
+ ? String(error.code ?? "")
473
+ : "";
474
+ return [code, error.message.replace(/bot\d+:[A-Za-z0-9_-]+/g, "bot[redacted]")]
475
+ .filter(Boolean)
476
+ .join(" ");
477
+ }
434
478
  function knownBotUsername(id) {
435
479
  if (id === 8369496408) {
436
480
  return "codex_remoteagent_bot";
@@ -206,8 +206,8 @@ export class BotManagementService {
206
206
  const transient = [];
207
207
  for (const bot of bots) {
208
208
  try {
209
- await this.fetchBotIdentity(bot.token);
210
- alive.push(bot);
209
+ const identity = await this.fetchBotIdentity(bot.token);
210
+ alive.push({ ...identity, index: bot.index });
211
211
  }
212
212
  catch (error) {
213
213
  const reason = error instanceof Error ? error.message : String(error);
@@ -453,7 +453,8 @@ export class BotManagementService {
453
453
  : singleTokenLine
454
454
  ? [singleTokenLine.slice("TELEGRAM_BOT_TOKEN=".length).trim()].filter(Boolean)
455
455
  : [];
456
- const usernames = usernameLine ? this.parseCsv(usernameLine.slice("TELEGRAM_BOT_USERNAMES=".length)) : [];
456
+ const configuredUsernames = usernameLine ? this.parseCsv(usernameLine.slice("TELEGRAM_BOT_USERNAMES=".length)) : [];
457
+ const usernames = await this.normalizeUsernamesFromTelegram(tokens, configuredUsernames);
457
458
  return {
458
459
  lines,
459
460
  tokens,
@@ -462,7 +463,7 @@ export class BotManagementService {
462
463
  };
463
464
  }
464
465
  async writeEnvConfig(originalLines, tokens, usernames, mainBotId) {
465
- const normalizedUsernames = this.normalizeUsernames(tokens, usernames);
466
+ const normalizedUsernames = await this.normalizeUsernamesFromTelegram(tokens, usernames);
466
467
  const normalizedMainBotId = this.resolveMainBotId(this.zipBots(tokens, normalizedUsernames), mainBotId);
467
468
  const nextLines = [];
468
469
  let hasMulti = false;
@@ -510,7 +511,23 @@ export class BotManagementService {
510
511
  await fs.writeFile(this.envPath, output, "utf8");
511
512
  }
512
513
  normalizeUsernames(tokens, usernames) {
513
- return tokens.map((token, index) => usernames[index]?.trim() || `bot_${this.tokenId(token)}`);
514
+ return tokens.map((token, index) => usernames[index]?.trim() || this.fallbackUsername(token));
515
+ }
516
+ async normalizeUsernamesFromTelegram(tokens, usernames) {
517
+ const normalized = [];
518
+ for (const token of tokens) {
519
+ try {
520
+ const identity = await this.fetchBotIdentity(token);
521
+ normalized.push(identity.username);
522
+ }
523
+ catch {
524
+ normalized.push(this.fallbackUsername(token));
525
+ }
526
+ }
527
+ return normalized;
528
+ }
529
+ fallbackUsername(token) {
530
+ return `bot_${this.tokenId(token)}`;
514
531
  }
515
532
  zipBots(tokens, usernames) {
516
533
  const normalizedUsernames = this.normalizeUsernames(tokens, usernames);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appback-remoteagent",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "Personal installable session server for continuing local AI work across PC and Telegram",
5
5
  "license": "MIT",
6
6
  "type": "module",