appback-remoteagent 0.23.0 → 0.23.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/README.md CHANGED
@@ -105,7 +105,7 @@ Current command surface implemented in `src/bot.ts`:
105
105
  | `/new` | Creates and binds a new session using the saved default mode in a new managed workspace |
106
106
  | `/switch <session>` | Rebinds this chat to an existing RemoteAgent session |
107
107
  | `/status` | Shows current session, workspace, provider, and sandbox state |
108
- | `/model [name]` | Lists selectable provider models or changes the current session model |
108
+ | `/model [name]` | Lists selectable provider models or changes the current session model. New Codex sessions default to `gpt-6-astra` with `medium` reasoning. Use `/model gpt-6-astra` for an existing session. |
109
109
  | `/sandbox [codex <mode>]` | Lists Codex sandbox choices or changes the current session sandbox |
110
110
  | `/option retry <count>` | Sets the automatic continuation turn limit and persists it to `~/.remoteagent/.env` |
111
111
  | `/option timeout <seconds>` | Sets the provider execution timeout and persists it to `~/.remoteagent/.env` |
@@ -67,6 +67,9 @@ export class CodexAdapter {
67
67
  if (request.model) {
68
68
  args.push("-m", request.model);
69
69
  }
70
+ if (request.model === "gpt-6-astra") {
71
+ args.push("-c", 'model_reasoning_effort="medium"');
72
+ }
70
73
  this.appendSandboxArgs(args, sandboxMode);
71
74
  args.push("-o", outputPath, "-C", request.cwd);
72
75
  this.appendPromptStdinArg(args);
@@ -82,6 +85,9 @@ export class CodexAdapter {
82
85
  if (request.model) {
83
86
  args.push("-m", request.model);
84
87
  }
88
+ if (request.model === "gpt-6-astra") {
89
+ args.push("-c", 'model_reasoning_effort="medium"');
90
+ }
85
91
  this.appendSandboxArgs(args, sandboxMode);
86
92
  args.push("-o", outputPath, request.sessionId);
87
93
  this.appendPromptStdinArg(args);
package/dist/bot.js CHANGED
@@ -1452,6 +1452,10 @@ async function runWithPendingAnimation(botToken, chatId, task) {
1452
1452
  typingStopped = true;
1453
1453
  return;
1454
1454
  }
1455
+ if (isTelegramRateLimitError(error)) {
1456
+ console.warn(`[telegram-chat-action] chat=${chatId} paused by Telegram rate limit: ${formatTelegramDeliveryError(error)}`);
1457
+ return;
1458
+ }
1455
1459
  console.warn(`[telegram-chat-action] chat=${chatId} failed: ${error instanceof Error ? error.message : String(error)}`);
1456
1460
  })
1457
1461
  .finally(() => {
@@ -1467,6 +1471,9 @@ async function runWithPendingAnimation(botToken, chatId, task) {
1467
1471
  try {
1468
1472
  const helpers = {
1469
1473
  reportProgress: async (chunks, parseMode, messageOptions) => {
1474
+ if (getTelegramRateLimitDelayMs(botToken) > 0) {
1475
+ return;
1476
+ }
1470
1477
  const normalized = await normalizeTelegramDelivery(chunks);
1471
1478
  const progressChunks = flattenChunks(normalized.chunks, 3900);
1472
1479
  if (progressChunks.length === 0 && normalized.documents.length === 0) {
@@ -1478,12 +1485,12 @@ async function runWithPendingAnimation(botToken, chatId, task) {
1478
1485
  ...(rendered.parseMode ? { parse_mode: rendered.parseMode } : {}),
1479
1486
  };
1480
1487
  for (const chunk of rendered.chunks) {
1481
- await sendTelegramMessage(botToken, chatId, chunk, extra).catch((error) => {
1488
+ await sendTelegramMessage(botToken, chatId, chunk, extra, "progress").catch((error) => {
1482
1489
  console.warn(`[telegram-progress-delivery] chat=${chatId} dropped progress message: ${formatTelegramDeliveryError(error)}`);
1483
1490
  });
1484
1491
  }
1485
1492
  if (normalized.documents.length > 0) {
1486
- await sendTelegramDocuments(botToken, chatId, normalized.documents).catch((error) => {
1493
+ await sendTelegramDocuments(botToken, chatId, normalized.documents, "progress").catch((error) => {
1487
1494
  console.warn(`[telegram-progress-delivery] chat=${chatId} dropped progress document(s): ${formatTelegramDeliveryError(error)}`);
1488
1495
  });
1489
1496
  }
@@ -3052,9 +3059,29 @@ async function isReadableTelegramDocument(filePath) {
3052
3059
  return false;
3053
3060
  }
3054
3061
  }
3055
- async function sendTelegramDocuments(botToken, chatId, documents) {
3062
+ async function sendTelegramDocuments(botToken, chatId, documents, deliveryClass = "final") {
3063
+ if (deliveryClass === "progress" && getTelegramRateLimitDelayMs(botToken) > 0) {
3064
+ return;
3065
+ }
3056
3066
  for (const document of documents) {
3057
- await sendTelegramDocument(botToken, chatId, document);
3067
+ if (deliveryClass === "progress") {
3068
+ await sendTelegramDocument(botToken, chatId, document);
3069
+ continue;
3070
+ }
3071
+ await serializeTelegramMessage(botToken, async () => {
3072
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
3073
+ await waitForTelegramRateLimit(botToken);
3074
+ try {
3075
+ await sendTelegramDocument(botToken, chatId, document);
3076
+ return;
3077
+ }
3078
+ catch (error) {
3079
+ if (!isTelegramRateLimitError(error) || attempt >= 3) {
3080
+ throw error;
3081
+ }
3082
+ }
3083
+ }
3084
+ });
3058
3085
  }
3059
3086
  }
3060
3087
  async function sendTelegramDocument(botToken, chatId, document) {
@@ -3081,7 +3108,14 @@ async function sendTelegramDocument(botToken, chatId, document) {
3081
3108
  }
3082
3109
  const payload = JSON.parse(stdout);
3083
3110
  if (!payload.ok || !payload.result) {
3084
- throw new Error(payload.description || "Telegram API sendDocument failed.");
3111
+ const retryAfterSeconds = payload.parameters?.retry_after;
3112
+ const retryAfterMs = typeof retryAfterSeconds === "number" && retryAfterSeconds > 0
3113
+ ? Math.ceil(retryAfterSeconds * 1000) + 250
3114
+ : undefined;
3115
+ if (payload.error_code === 429 || retryAfterMs !== undefined) {
3116
+ registerTelegramRateLimit(botToken, retryAfterMs ?? 5_250);
3117
+ }
3118
+ throw new TelegramApiError("sendDocument", payload.description || "Telegram API sendDocument failed.", payload.error_code, retryAfterMs);
3085
3119
  }
3086
3120
  return payload.result;
3087
3121
  }
@@ -3104,11 +3138,23 @@ async function deleteTelegramMessage(botToken, chatId, messageId) {
3104
3138
  throw new Error(payload.description || "Telegram API deleteMessage failed.");
3105
3139
  }
3106
3140
  }
3107
- async function sendTelegramMessage(botToken, chatId, text, extra) {
3141
+ async function sendTelegramMessage(botToken, chatId, text, extra, deliveryClass = "final") {
3142
+ if (deliveryClass === "progress") {
3143
+ if (getTelegramRateLimitDelayMs(botToken) > 0) {
3144
+ throw new TelegramDeliveryDeferredError("Telegram progress delivery skipped during rate-limit cooldown.");
3145
+ }
3146
+ return sendTelegramMessageNow(botToken, chatId, text, extra, deliveryClass);
3147
+ }
3148
+ return serializeTelegramMessage(botToken, () => sendTelegramMessageNow(botToken, chatId, text, extra, deliveryClass));
3149
+ }
3150
+ async function sendTelegramMessageNow(botToken, chatId, text, extra, deliveryClass) {
3108
3151
  const startedAt = Date.now();
3109
3152
  try {
3110
3153
  let lastError;
3111
3154
  for (let attempt = 1; attempt <= 3; attempt += 1) {
3155
+ if (deliveryClass === "final") {
3156
+ await waitForTelegramRateLimit(botToken);
3157
+ }
3112
3158
  try {
3113
3159
  try {
3114
3160
  return await callTelegramApi(botToken, "sendMessage", {
@@ -3132,6 +3178,15 @@ async function sendTelegramMessage(botToken, chatId, text, extra) {
3132
3178
  }
3133
3179
  catch (error) {
3134
3180
  lastError = error;
3181
+ if (isTelegramRateLimitError(error)) {
3182
+ if (deliveryClass === "progress" || attempt >= 3) {
3183
+ throw error;
3184
+ }
3185
+ const delayMs = getTelegramRateLimitDelayMs(botToken);
3186
+ console.warn(`[telegram-sendMessage-rate-limit] chat=${chatId} attempt=${attempt}/3 retryAfterMs=${delayMs}`);
3187
+ await waitForTelegramRateLimit(botToken);
3188
+ continue;
3189
+ }
3135
3190
  if (attempt >= 3 || !isRetryableTelegramDeliveryError(error)) {
3136
3191
  throw error;
3137
3192
  }
@@ -3163,6 +3218,9 @@ function stripTelegramHtml(value) {
3163
3218
  .replace(/&amp;/g, "&");
3164
3219
  }
3165
3220
  async function sendTelegramChatAction(botToken, chatId, action) {
3221
+ if (getTelegramRateLimitDelayMs(botToken) > 0) {
3222
+ return;
3223
+ }
3166
3224
  await callTelegramApi(botToken, "sendChatAction", {
3167
3225
  chat_id: String(chatId),
3168
3226
  action,
@@ -3195,20 +3253,78 @@ async function callTelegramApi(botToken, method, params) {
3195
3253
  }
3196
3254
  const payload = JSON.parse(stdout);
3197
3255
  if (!payload.ok) {
3198
- throw new TelegramApiError(method, payload.description || `Telegram API ${method} failed.`);
3256
+ const retryAfterSeconds = payload.parameters?.retry_after;
3257
+ const retryAfterMs = typeof retryAfterSeconds === "number" && retryAfterSeconds > 0
3258
+ ? Math.ceil(retryAfterSeconds * 1000) + 250
3259
+ : undefined;
3260
+ if (payload.error_code === 429 || retryAfterMs !== undefined) {
3261
+ registerTelegramRateLimit(botToken, retryAfterMs ?? 5_250);
3262
+ }
3263
+ throw new TelegramApiError(method, payload.description || `Telegram API ${method} failed.`, payload.error_code, retryAfterMs);
3199
3264
  }
3200
3265
  return payload.result;
3201
3266
  }
3202
3267
  class TelegramApiError extends Error {
3203
3268
  method;
3204
3269
  description;
3205
- constructor(method, description) {
3270
+ errorCode;
3271
+ retryAfterMs;
3272
+ constructor(method, description, errorCode, retryAfterMs) {
3206
3273
  super(description);
3207
3274
  this.method = method;
3208
3275
  this.description = description;
3276
+ this.errorCode = errorCode;
3277
+ this.retryAfterMs = retryAfterMs;
3209
3278
  this.name = "TelegramApiError";
3210
3279
  }
3211
3280
  }
3281
+ class TelegramDeliveryDeferredError extends Error {
3282
+ constructor(message) {
3283
+ super(message);
3284
+ this.name = "TelegramDeliveryDeferredError";
3285
+ }
3286
+ }
3287
+ const telegramRateLimitUntil = new Map();
3288
+ const telegramMessageQueues = new Map();
3289
+ function telegramBotKey(botToken) {
3290
+ return botToken.split(":", 1)[0] || botToken;
3291
+ }
3292
+ function registerTelegramRateLimit(botToken, retryAfterMs) {
3293
+ const key = telegramBotKey(botToken);
3294
+ const until = Date.now() + Math.max(250, retryAfterMs);
3295
+ telegramRateLimitUntil.set(key, Math.max(telegramRateLimitUntil.get(key) ?? 0, until));
3296
+ }
3297
+ function getTelegramRateLimitDelayMs(botToken) {
3298
+ const key = telegramBotKey(botToken);
3299
+ const until = telegramRateLimitUntil.get(key) ?? 0;
3300
+ const delayMs = until - Date.now();
3301
+ if (delayMs <= 0) {
3302
+ telegramRateLimitUntil.delete(key);
3303
+ return 0;
3304
+ }
3305
+ return delayMs;
3306
+ }
3307
+ async function waitForTelegramRateLimit(botToken) {
3308
+ const delayMs = getTelegramRateLimitDelayMs(botToken);
3309
+ if (delayMs > 0) {
3310
+ await sleep(delayMs);
3311
+ }
3312
+ }
3313
+ async function serializeTelegramMessage(botToken, task) {
3314
+ const key = telegramBotKey(botToken);
3315
+ const previous = telegramMessageQueues.get(key) ?? Promise.resolve();
3316
+ const current = previous.catch(() => undefined).then(task);
3317
+ const tail = current.then(() => undefined, () => undefined);
3318
+ telegramMessageQueues.set(key, tail);
3319
+ try {
3320
+ return await current;
3321
+ }
3322
+ finally {
3323
+ if (telegramMessageQueues.get(key) === tail) {
3324
+ telegramMessageQueues.delete(key);
3325
+ }
3326
+ }
3327
+ }
3212
3328
  function summarizeCurlTelegramError(error, method) {
3213
3329
  if (!(error instanceof Error)) {
3214
3330
  return `Telegram API ${method} failed: ${String(error)}`;
@@ -3229,6 +3345,9 @@ function isRetryableTelegramDeliveryError(error) {
3229
3345
  const message = formatTelegramDeliveryError(error);
3230
3346
  return /timed out|timeout|Bad Gateway|502|503|504|ECONNRESET|connection reset|EAI_AGAIN|ENOTFOUND/i.test(message);
3231
3347
  }
3348
+ function isTelegramRateLimitError(error) {
3349
+ return error instanceof TelegramApiError && (error.errorCode === 429 || error.retryAfterMs !== undefined);
3350
+ }
3232
3351
  function isTelegramForbiddenError(error) {
3233
3352
  if (error instanceof GrammyError) {
3234
3353
  return error.error_code === 403 || /^Forbidden:/i.test(error.description);
package/dist/index.js CHANGED
@@ -20,7 +20,6 @@ import { ProviderRecoveryService } from "./services/provider-recovery-service.js
20
20
  import { computePolicyPollIntervalMs, computeRecentMessageRanks } from "./services/polling-policy.js";
21
21
  import { terminateAllSpawnedExecutions } from "./adapters/windows-shell.js";
22
22
  import { buildProviderEnv } from "./adapters/runtime-env.js";
23
- import { setTelegramCommandMenu } from "./telegram-command-menu.js";
24
23
  import { buildBotInfoFromIdentity, buildFallbackBotInfo } from "./telegram-bot-identity.js";
25
24
  const execFileAsync = promisify(execFile);
26
25
  const TELEGRAM_GET_UPDATES_HTTP_TIMEOUT_SECONDS = 30;
@@ -72,20 +71,14 @@ async function main() {
72
71
  }
73
72
  const botInfos = await Promise.all(config.telegramBotTokens.map((token, index) => resolveBotInfo(token, index, config.telegramBotUsernames[index])));
74
73
  const bots = config.telegramBotTokens.map((token, index) => createBot(token, bridge, botManagement, botInfos[index]));
75
- if (config.telegramCommandMenuEnabled) {
76
- for (const bot of bots) {
77
- const username = bot.botInfo.username;
78
- await configureTelegramCommandMenu(bot).catch((error) => {
79
- console.error(`Failed to configure command menu for @${username}:`, error);
80
- });
81
- console.log(`Bot @${username} is ready`);
82
- }
74
+ if (!config.telegramCommandMenuEnabled) {
75
+ console.log("Telegram command menu registration is disabled.");
83
76
  }
84
77
  else {
85
- console.log("Telegram command menu registration is disabled.");
86
- for (const bot of bots) {
87
- console.log(`Bot @${bot.botInfo.username} is ready`);
88
- }
78
+ console.log("Telegram command menu startup refresh is skipped; use /option command-menu refresh to apply changes.");
79
+ }
80
+ for (const bot of bots) {
81
+ console.log(`Bot @${bot.botInfo.username} is ready`);
89
82
  }
90
83
  await botManagement.reportPendingOperationResult().catch((error) => {
91
84
  console.error("Failed to report pending bot operation result:", error);
@@ -140,26 +133,6 @@ function startArtifactCleanupSchedule(memoryService) {
140
133
  }, config.artifactCleanupIntervalMs);
141
134
  interval.unref();
142
135
  }
143
- async function configureTelegramCommandMenu(bot) {
144
- const token = bot.token;
145
- if (!token) {
146
- throw new Error("Telegram bot token is unavailable for command menu registration.");
147
- }
148
- let lastError;
149
- for (let attempt = 1; attempt <= 3; attempt += 1) {
150
- try {
151
- await setTelegramCommandMenu(token);
152
- return;
153
- }
154
- catch (error) {
155
- lastError = error;
156
- if (attempt < 3) {
157
- await sleep(1000 * attempt);
158
- }
159
- }
160
- }
161
- throw lastError;
162
- }
163
136
  main().catch((error) => {
164
137
  console.error("RemoteAgent fatal error:", error);
165
138
  releaseProcessLockSync();
@@ -5,10 +5,10 @@ import path from "node:path";
5
5
  import { stopSpawnedExecution } from "../adapters/windows-shell.js";
6
6
  import { CODEX_USAGE_FALLBACK_MODEL, CodexUsageFallbackService, parseCodexUsageLimit, } from "./codex-usage-fallback-service.js";
7
7
  const MODEL_PRESETS = {
8
- codex: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.6", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark", "gpt-5.2", "gpt-5.1-codex-max"],
8
+ codex: ["gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.6", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark", "gpt-5.2", "gpt-5.1-codex-max"],
9
9
  claude: ["sonnet", "opus", "haiku"],
10
10
  };
11
- const DEFAULT_CODEX_MODEL = "gpt-5.6-sol";
11
+ const DEFAULT_CODEX_MODEL = "gpt-6-astra";
12
12
  export class BridgeService {
13
13
  store;
14
14
  adapters;
@@ -367,7 +367,8 @@ export class BridgeService {
367
367
  return responses.map((response) => {
368
368
  const sessionLabel = response.publicSessionId ?? response.sessionId;
369
369
  const modelLabel = response.model?.trim() || this.defaultModelFor(response.provider);
370
- const header = `[${response.provider.toUpperCase()} | ${modelLabel} | ${sessionLabel}]`;
370
+ const effortLabel = response.provider === "codex" && modelLabel === "gpt-6-astra" ? " | medium" : "";
371
+ const header = `[${response.provider.toUpperCase()} | ${modelLabel}${effortLabel} | ${sessionLabel}]`;
371
372
  return `${header}\n${response.output}`;
372
373
  });
373
374
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appback-remoteagent",
3
- "version": "0.23.0",
3
+ "version": "0.23.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",
@@ -34,6 +34,12 @@ await fs.chmod(fakeCodex, 0o755);
34
34
 
35
35
  const { CodexAdapter } = await import(path.join(root, "dist", "adapters", "codex-adapter.js"));
36
36
  const adapter = new CodexAdapter(fakeCodex, 5000, "read-only");
37
+ for (const method of ["buildExecArgs", "buildResumeArgs"]) {
38
+ const args = adapter[method]({model: "gpt-6-astra", cwd: tmp, sessionId: "stream-thread"}, path.join(tmp, "output"), "read-only");
39
+ if (args[args.indexOf("-m") + 1] !== "gpt-6-astra" || !args.some((arg, i) => arg === "-c" && args[i + 1] === 'model_reasoning_effort="medium"')) {
40
+ throw new Error(`Astra medium missing from ${method}`);
41
+ }
42
+ }
37
43
  const progress = [];
38
44
  let settled = false;
39
45
  const responsePromise = adapter.send({
@@ -18,7 +18,7 @@ const dataDir = path.join(root, "data");
18
18
  const workspaceRoot = path.join(root, "workspaces");
19
19
  const defaultWorkspace = path.join(root, "default-workspace");
20
20
  const fallbackStatePath = path.join(dataDir, "codex-usage-fallback.json");
21
- const primaryModel = "gpt-5.6-sol";
21
+ const primaryModel = "gpt-6-astra";
22
22
  const usageError = "You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Aug 27th, 2099 3:52 AM.";
23
23
 
24
24
  try {
@@ -63,6 +63,7 @@ try {
63
63
  );
64
64
 
65
65
  let bridge = createBridge();
66
+ assert.equal(bridge.formatResponses([{provider: "codex", model: primaryModel, publicSessionId: "S081", output: "done"}])[0], "[CODEX | gpt-6-astra | medium | S081]\ndone");
66
67
  const started = await bridge.startSession("test-bot", "test-chat", "codex");
67
68
  const originalSessionId = started.session.sessionId;
68
69
  const first = await bridge.routeMessage("test-bot", "test-chat", "first request", async (response) => {
@@ -12,6 +12,7 @@ const workspaceRoot = path.join(tmp, "workspaces");
12
12
  const binDir = path.join(tmp, "bin");
13
13
  const telegramCalls = path.join(tmp, "telegram-calls.jsonl");
14
14
  const capturedDocument = path.join(tmp, "captured-document.ra-secrets");
15
+ const rateLimitOnce = path.join(tmp, "telegram-rate-limit-once");
15
16
 
16
17
  await fs.mkdir(workspace, { recursive: true });
17
18
  await fs.mkdir(workspaceRoot, { recursive: true });
@@ -27,6 +28,7 @@ document_path=""
27
28
  for arg in "$@"; do
28
29
  case "$arg" in
29
30
  https://api.telegram.org/bot*/sendMessage) method="sendMessage" ;;
31
+ https://api.telegram.org/bot*/sendChatAction) method="sendChatAction" ;;
30
32
  https://api.telegram.org/bot*/editMessageText) method="editMessageText" ;;
31
33
  https://api.telegram.org/bot*/deleteMessage) method="deleteMessage" ;;
32
34
  https://api.telegram.org/bot*/sendDocument) method="sendDocument" ;;
@@ -40,6 +42,11 @@ done
40
42
  text_b64="$(printf '%s' "$text" | base64 -w 0)"
41
43
  reply_markup_b64="$(printf '%s' "$reply_markup" | base64 -w 0)"
42
44
  printf '%s\\t%s\\t%s\\t%s\\n' "$method" "$chat_id" "$text_b64" "$reply_markup_b64" >> ${JSON.stringify(telegramCalls)}
45
+ if [[ "$method" == "sendMessage" && -n "\${TELEGRAM_SELFTEST_RATE_LIMIT_FILE:-}" && -f "\${TELEGRAM_SELFTEST_RATE_LIMIT_FILE}" ]]; then
46
+ rm -f "\${TELEGRAM_SELFTEST_RATE_LIMIT_FILE}"
47
+ printf '{"ok":false,"error_code":429,"description":"Too Many Requests: retry after 1","parameters":{"retry_after":1}}'
48
+ exit 0
49
+ fi
43
50
  case "$method" in
44
51
  sendMessage|editMessageText)
45
52
  printf '{"ok":true,"result":{"message_id":1001}}'
@@ -69,6 +76,7 @@ process.env.TELEGRAM_AUTO_PROGRESS_MAX_TURNS = "1";
69
76
  process.env.TELEGRAM_EMPTY_RESPONSE_RETRIES = "0";
70
77
  process.env.TELEGRAM_RETRYABLE_ERROR_RETRIES = "0";
71
78
  process.env.LOCAL_UI_ENABLED = "false";
79
+ process.env.TELEGRAM_SELFTEST_RATE_LIMIT_FILE = rateLimitOnce;
72
80
 
73
81
  const [
74
82
  { createBot },
@@ -320,6 +328,13 @@ await send("/secret set API_TOKEN telegram-secret-export-value");
320
328
  await send("/secret export REMOTEAGENT_TRANSFER_PASSPHRASE API_TOKEN");
321
329
  await send("같은 값을 봐야하는데 로직문제네? 확인해줘\\n이미 수정되어 있을 수 있어.\\n나한테 수정했다고 보고했었거든");
322
330
  await send("/state");
331
+ await fs.writeFile(rateLimitOnce, "once", "utf8");
332
+ const rateLimitStartedAt = Date.now();
333
+ await send("/status");
334
+ const rateLimitElapsedMs = Date.now() - rateLimitStartedAt;
335
+ if (rateLimitElapsedMs < 1000) {
336
+ throw new Error(`Telegram retry_after was not honored: elapsedMs=${rateLimitElapsedMs}`);
337
+ }
323
338
 
324
339
  const state = JSON.parse(await fs.readFile(path.join(dataDir, "state.json"), "utf8"));
325
340
  const sessions = Object.values(state.sessions);
@@ -843,6 +858,7 @@ console.log(JSON.stringify({
843
858
  usageLimitFallback: true,
844
859
  longTelegramTextStoredAsFile: true,
845
860
  telegramSendMessages: evidenceCalls.filter((call) => call.method === "sendMessage").length,
861
+ telegramRateLimitElapsedMs: rateLimitElapsedMs,
846
862
  }, null, 2));
847
863
 
848
864
  process.exit(0);