cc-claw 0.18.3 → 0.18.4

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.
Files changed (2) hide show
  1. package/dist/cli.js +160 -42
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var VERSION;
33
33
  var init_version = __esm({
34
34
  "src/version.ts"() {
35
35
  "use strict";
36
- VERSION = true ? "0.18.3" : (() => {
36
+ VERSION = true ? "0.18.4" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -11154,6 +11154,7 @@ var init_detect = __esm({
11154
11154
  // src/agent.ts
11155
11155
  var agent_exports = {};
11156
11156
  __export(agent_exports, {
11157
+ CONTENT_SILENCE_TIMEOUT_ERROR: () => CONTENT_SILENCE_TIMEOUT_ERROR,
11157
11158
  FIRST_RESPONSE_TIMEOUT_ERROR: () => FIRST_RESPONSE_TIMEOUT_ERROR,
11158
11159
  FREE_SLOTS_EXHAUSTED: () => FREE_SLOTS_EXHAUSTED,
11159
11160
  askAgent: () => askAgent,
@@ -11277,6 +11278,20 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11277
11278
  }
11278
11279
  }, frTimeoutMs);
11279
11280
  }
11281
+ let contentSilenceTimer;
11282
+ const silenceTimeoutMs = CONTENT_SILENCE_TIMEOUT_MS;
11283
+ function resetContentSilenceTimer() {
11284
+ if (silenceTimeoutMs <= 0) return;
11285
+ if (contentSilenceTimer) clearTimeout(contentSilenceTimer);
11286
+ contentSilenceTimer = setTimeout(() => {
11287
+ if (cancelState.cancelled || timedOut) return;
11288
+ warn(`[agent] Content silence timeout after ${silenceTimeoutMs / 1e3}s for ${adapter.id} \u2014 no content events, killing`);
11289
+ timedOut = true;
11290
+ cancelState.__contentSilenceTimeout = true;
11291
+ killProcessGroup(proc, "SIGTERM");
11292
+ sigkillTimer = setTimeout(() => killProcessGroup(proc, "SIGKILL"), 3e3);
11293
+ }, silenceTimeoutMs);
11294
+ }
11280
11295
  rl2.on("line", (line) => {
11281
11296
  if (!line.trim()) return;
11282
11297
  if (firstLine) {
@@ -11299,6 +11314,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11299
11314
  case "init":
11300
11315
  log(`[agent] Session init at ${elapsed()}`);
11301
11316
  if (ev.sessionId) sessionId = ev.sessionId;
11317
+ resetContentSilenceTimer();
11302
11318
  break;
11303
11319
  case "text":
11304
11320
  if (!gotModelContent) {
@@ -11308,6 +11324,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11308
11324
  firstResponseTimer = void 0;
11309
11325
  }
11310
11326
  }
11327
+ resetContentSilenceTimer();
11311
11328
  if (ev.text) {
11312
11329
  accumulatedText = appendTextChunk(accumulatedText, ev.text);
11313
11330
  if (opts?.onStream) opts.onStream(ev.text);
@@ -11321,6 +11338,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11321
11338
  firstResponseTimer = void 0;
11322
11339
  }
11323
11340
  }
11341
+ resetContentSilenceTimer();
11324
11342
  if (ev.text) {
11325
11343
  accumulatedThinking = appendTextChunk(accumulatedThinking, ev.text);
11326
11344
  if (opts?.onThinking) opts.onThinking(ev.text);
@@ -11334,6 +11352,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11334
11352
  firstResponseTimer = void 0;
11335
11353
  }
11336
11354
  }
11355
+ resetContentSilenceTimer();
11337
11356
  sawToolEvents = true;
11338
11357
  if (opts?.onToolAction && ev.toolName) {
11339
11358
  const toolInput = ev.toolInput ?? {};
@@ -11360,6 +11379,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11360
11379
  }
11361
11380
  break;
11362
11381
  case "tool_end":
11382
+ resetContentSilenceTimer();
11363
11383
  if (opts?.onToolAction) {
11364
11384
  const pending = ev.toolId ? pendingTools.get(ev.toolId) : void 0;
11365
11385
  if (pending) {
@@ -11413,6 +11433,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11413
11433
  proc.on("error", (err) => {
11414
11434
  clearTimeout(spawnTimeout);
11415
11435
  if (firstResponseTimer) clearTimeout(firstResponseTimer);
11436
+ if (contentSilenceTimer) clearTimeout(contentSilenceTimer);
11416
11437
  if (sigkillTimer) clearTimeout(sigkillTimer);
11417
11438
  rl2.close();
11418
11439
  cancelState.process = void 0;
@@ -11424,6 +11445,10 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11424
11445
  clearTimeout(firstResponseTimer);
11425
11446
  firstResponseTimer = void 0;
11426
11447
  }
11448
+ if (contentSilenceTimer) {
11449
+ clearTimeout(contentSilenceTimer);
11450
+ contentSilenceTimer = void 0;
11451
+ }
11427
11452
  if (sigkillTimer) {
11428
11453
  clearTimeout(sigkillTimer);
11429
11454
  sigkillTimer = void 0;
@@ -11444,6 +11469,11 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11444
11469
  reject(new Error(`${FIRST_RESPONSE_TIMEOUT_ERROR}: No response from ${adapter.id} within ${frTimeoutMs / 1e3}s${stderr ? ` \u2014 ${stderr.slice(-300)}` : ""}`));
11445
11470
  return;
11446
11471
  }
11472
+ if (cancelState.__contentSilenceTimeout) {
11473
+ delete cancelState.__contentSilenceTimeout;
11474
+ reject(new Error(`${CONTENT_SILENCE_TIMEOUT_ERROR}: ${adapter.id} produced no content events for ${silenceTimeoutMs / 1e3}s after init`));
11475
+ return;
11476
+ }
11447
11477
  let msg = `Spawn timeout after ${effectiveTimeout / 1e3}s`;
11448
11478
  if (pendingTools.size > 0) {
11449
11479
  const tools2 = Array.from(pendingTools.values()).map((t) => typeof t === "string" ? t : t.name).join(", ");
@@ -11796,6 +11826,26 @@ async function askAgentImpl(chatId, userMessage, opts) {
11796
11826
  })();
11797
11827
  result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, retryOpts);
11798
11828
  }
11829
+ } else if (errMsg.startsWith(CONTENT_SILENCE_TIMEOUT_ERROR) && existingSessionId) {
11830
+ warn(`[agent] Content silence on ${adapter.id} \u2014 clearing session ${existingSessionId} and retrying fresh`);
11831
+ clearSession(chatId);
11832
+ if (useGeminiRotation) {
11833
+ const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
11834
+ const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
11835
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, geminiRotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
11836
+ } else if (useBackendRotation) {
11837
+ const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
11838
+ result = await spawnWithSlotRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, backendRotationMode, allowPaid, spawnOpts, rotationCb);
11839
+ } else {
11840
+ const retryOpts = (() => {
11841
+ if (adapter.id !== "gemini") return spawnOpts;
11842
+ const geminiAdapter = adapter;
11843
+ const { env, slot } = geminiAdapter.resolveSlotEnv(chatId);
11844
+ if (slot) return { ...spawnOpts, envOverride: env };
11845
+ return spawnOpts;
11846
+ })();
11847
+ result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, retryOpts);
11848
+ }
11799
11849
  } else {
11800
11850
  if (!isSyntheticChatId(chatId)) {
11801
11851
  try {
@@ -11900,7 +11950,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
11900
11950
  if (!flag) return args;
11901
11951
  return [...args, ...flag, mcpConfigPath];
11902
11952
  }
11903
- var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_ERROR, FREE_SLOTS_EXHAUSTED, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
11953
+ var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_ERROR, FIRST_RESPONSE_TIMEOUT_ERROR, FREE_SLOTS_EXHAUSTED, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
11904
11954
  var init_agent = __esm({
11905
11955
  "src/agent.ts"() {
11906
11956
  "use strict";
@@ -11925,6 +11975,8 @@ var init_agent = __esm({
11925
11975
  chatLocks = /* @__PURE__ */ new Map();
11926
11976
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
11927
11977
  FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
11978
+ CONTENT_SILENCE_TIMEOUT_MS = parseInt(process.env.CONTENT_SILENCE_TIMEOUT_MS ?? "90000", 10);
11979
+ CONTENT_SILENCE_TIMEOUT_ERROR = "CONTENT_SILENCE_TIMEOUT";
11928
11980
  FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
11929
11981
  FREE_SLOTS_EXHAUSTED = "FREE_SLOTS_EXHAUSTED";
11930
11982
  GEMINI_FALLBACK_CHAIN = {
@@ -19820,7 +19872,14 @@ var init_commands = __esm({
19820
19872
 
19821
19873
  // src/router/callbacks.ts
19822
19874
  import { readFile as readFile7 } from "fs/promises";
19823
- async function handleCallback(chatId, data, channel) {
19875
+ async function handleCallback(chatId, data, channel, messageId) {
19876
+ const replaceWithText = async (text) => {
19877
+ if (messageId && channel.editText) {
19878
+ await channel.editText(chatId, messageId, text, "plain");
19879
+ } else {
19880
+ await channel.sendText(chatId, text, { parseMode: "plain" });
19881
+ }
19882
+ };
19824
19883
  if (data.startsWith("menu:")) {
19825
19884
  const action = data.slice(5);
19826
19885
  const synth = { chatId, messageId: "", text: "", senderName: "User", type: "command", source: "telegram", command: "", commandArgs: "" };
@@ -20056,7 +20115,7 @@ ${PERM_MODES[chosen]}`,
20056
20115
  return;
20057
20116
  }
20058
20117
  removePendingPlan(chatId);
20059
- await channel.sendText(chatId, "\u2705 Approved. Executing...", { parseMode: "plain" });
20118
+ await replaceWithText("\u2705 Approved. Executing...");
20060
20119
  bypassBusyCheck.add(chatId);
20061
20120
  const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
20062
20121
  const overrideMsg = `[SYSTEM: Planning mode disabled. Execution APPROVED. Please execute the plan.]
@@ -20549,14 +20608,14 @@ ${rotationNote}`, { parseMode: "html" });
20549
20608
  }
20550
20609
  } else if (action === "discard") {
20551
20610
  pendingInterrupts.delete(targetChatId);
20552
- await channel.sendText(chatId, "\u{1F5D1} Message discarded.", { parseMode: "plain" });
20611
+ await replaceWithText("\u{1F5D1} Message discarded.");
20553
20612
  } else {
20554
20613
  await channel.sendText(chatId, "Message already processed or expired.", { parseMode: "plain" });
20555
20614
  }
20556
20615
  } else if (data.startsWith("sq:cancel:")) {
20557
20616
  const sqId = data.slice("sq:cancel:".length);
20558
20617
  stopAgent(sqId);
20559
- await channel.sendText(chatId, "\u{1F5FA} Side quest cancelled.", { parseMode: "plain" });
20618
+ await replaceWithText("\u{1F5FA} Side quest cancelled.");
20560
20619
  } else if (data.startsWith("fallback:")) {
20561
20620
  const parts = data.split(":");
20562
20621
  const targetBackend = parts[1];
@@ -21183,13 +21242,14 @@ function makeLiveStatus(chatId, channel, modelLabel, verboseLevel, showThinking)
21183
21242
  };
21184
21243
  return { liveStatus, toolCb };
21185
21244
  }
21186
- var FLUSH_INTERVAL_MS, MAX_THINKING_CHARS, TRIM_THRESHOLD, MAX_ENTRIES, LiveStatusMessage;
21245
+ var FLUSH_INTERVAL_DM_MS, FLUSH_INTERVAL_GROUP_MS, MAX_THINKING_CHARS, TRIM_THRESHOLD, MAX_ENTRIES, LiveStatusMessage;
21187
21246
  var init_live_status = __esm({
21188
21247
  "src/router/live-status.ts"() {
21189
21248
  "use strict";
21190
21249
  init_log();
21191
21250
  init_helpers();
21192
- FLUSH_INTERVAL_MS = 1e3;
21251
+ FLUSH_INTERVAL_DM_MS = 1e3;
21252
+ FLUSH_INTERVAL_GROUP_MS = 3e3;
21193
21253
  MAX_THINKING_CHARS = 800;
21194
21254
  TRIM_THRESHOLD = 3500;
21195
21255
  MAX_ENTRIES = 200;
@@ -21211,6 +21271,11 @@ var init_live_status = __esm({
21211
21271
  nextFlushAllowedAt = 0;
21212
21272
  /** Tracks whether entries have been trimmed at least once (for display hint). */
21213
21273
  hasTrimmed = false;
21274
+ /** Resolve flush interval based on chat type (group chats are rate-limited more aggressively). */
21275
+ get flushIntervalMs() {
21276
+ const numericId = parseInt(this.chatId);
21277
+ return numericId < 0 ? FLUSH_INTERVAL_GROUP_MS : FLUSH_INTERVAL_DM_MS;
21278
+ }
21214
21279
  /** Send the initial status message. Must be called before adding entries. */
21215
21280
  async init() {
21216
21281
  if (!this.channel.sendTextReturningId) return;
@@ -21219,7 +21284,7 @@ var init_live_status = __esm({
21219
21284
  this.messageId = await this.channel.sendTextReturningId(this.chatId, initial, "plain") ?? null;
21220
21285
  if (this.messageId) {
21221
21286
  this.flushTimer = setInterval(() => this.flush().catch(() => {
21222
- }), FLUSH_INTERVAL_MS);
21287
+ }), this.flushIntervalMs);
21223
21288
  }
21224
21289
  } catch (err) {
21225
21290
  log(`[live-status] init failed: ${err}`);
@@ -22932,7 +22997,26 @@ ${body.replace(/<[^>]*>/g, "").trim()}</code>
22932
22997
  });
22933
22998
 
22934
22999
  // src/channels/telegram.ts
22935
- import { Bot, InlineKeyboard, InputFile } from "grammy";
23000
+ import { Bot, GrammyError, InlineKeyboard, InputFile } from "grammy";
23001
+ async function withRetry(label2, fn) {
23002
+ for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
23003
+ try {
23004
+ return await fn();
23005
+ } catch (err) {
23006
+ if (err instanceof GrammyError && err.error_code === 429) {
23007
+ const retrySec = Math.min(err.parameters?.retry_after ?? FALLBACK_RETRY_SEC, MAX_RETRY_WAIT_SEC);
23008
+ if (attempt < MAX_RETRIES2) {
23009
+ warn(`[telegram] 429 on ${label2} (attempt ${attempt + 1}/${MAX_RETRIES2}) \u2014 retrying in ${retrySec}s`);
23010
+ await new Promise((r) => setTimeout(r, retrySec * 1e3));
23011
+ continue;
23012
+ }
23013
+ warn(`[telegram] 429 on ${label2} \u2014 exhausted ${MAX_RETRIES2} retries, giving up`);
23014
+ }
23015
+ throw err;
23016
+ }
23017
+ }
23018
+ throw new Error(`withRetry: unreachable`);
23019
+ }
22936
23020
  function isFastPathMessage(msg) {
22937
23021
  if (msg.type === "command" && msg.command && FAST_PATH_COMMANDS.has(msg.command)) {
22938
23022
  return true;
@@ -22952,13 +23036,16 @@ function numericChatId(chatId) {
22952
23036
  const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
22953
23037
  return parseInt(raw);
22954
23038
  }
22955
- var FAST_PATH_COMMANDS, TelegramChannel;
23039
+ var MAX_RETRIES2, FALLBACK_RETRY_SEC, MAX_RETRY_WAIT_SEC, FAST_PATH_COMMANDS, TelegramChannel;
22956
23040
  var init_telegram2 = __esm({
22957
23041
  "src/channels/telegram.ts"() {
22958
23042
  "use strict";
22959
23043
  init_telegram();
22960
23044
  init_log();
22961
23045
  init_store5();
23046
+ MAX_RETRIES2 = 3;
23047
+ FALLBACK_RETRY_SEC = 3;
23048
+ MAX_RETRY_WAIT_SEC = 30;
22962
23049
  FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
22963
23050
  TelegramChannel = class {
22964
23051
  name = "telegram";
@@ -23109,11 +23196,12 @@ var init_telegram2 = __esm({
23109
23196
  return;
23110
23197
  }
23111
23198
  const data = ctx.callbackQuery.data;
23199
+ const messageId = ctx.callbackQuery.message?.message_id?.toString();
23112
23200
  ctx.answerCallbackQuery().catch(() => {
23113
23201
  });
23114
23202
  (async () => {
23115
23203
  for (const handler2 of this.callbackHandlers) {
23116
- await handler2(chatId, data, this);
23204
+ await handler2(chatId, data, this, messageId);
23117
23205
  }
23118
23206
  })().catch((err) => {
23119
23207
  error("[telegram] Callback handler error:", err);
@@ -23166,7 +23254,10 @@ var init_telegram2 = __esm({
23166
23254
  if (parseMode === "plain") {
23167
23255
  const plainChunks = splitMessage(text);
23168
23256
  for (const chunk of plainChunks) {
23169
- const sent = await this.bot.api.sendMessage(numericChatId(chatId), chunk, { ...threadOpts, ...replyOpts });
23257
+ const sent = await withRetry(
23258
+ "sendText:plain",
23259
+ () => this.bot.api.sendMessage(numericChatId(chatId), chunk, { ...threadOpts, ...replyOpts })
23260
+ );
23170
23261
  this.trackAgentMessage(sent.message_id, chatId);
23171
23262
  }
23172
23263
  return;
@@ -23175,32 +23266,44 @@ var init_telegram2 = __esm({
23175
23266
  const chunks = splitMessage(formatted);
23176
23267
  for (const chunk of chunks) {
23177
23268
  try {
23178
- const sent = await this.bot.api.sendMessage(numericChatId(chatId), chunk, {
23179
- parse_mode: "HTML",
23180
- ...threadOpts,
23181
- ...replyOpts
23182
- });
23269
+ const sent = await withRetry(
23270
+ "sendText:html",
23271
+ () => this.bot.api.sendMessage(numericChatId(chatId), chunk, {
23272
+ parse_mode: "HTML",
23273
+ ...threadOpts,
23274
+ ...replyOpts
23275
+ })
23276
+ );
23183
23277
  this.trackAgentMessage(sent.message_id, chatId);
23184
23278
  } catch {
23185
- const sent = await this.bot.api.sendMessage(
23186
- numericChatId(chatId),
23187
- chunk.replace(/<[^>]+>/g, ""),
23188
- { ...threadOpts, ...replyOpts }
23279
+ const sent = await withRetry(
23280
+ "sendText:fallback",
23281
+ () => this.bot.api.sendMessage(
23282
+ numericChatId(chatId),
23283
+ chunk.replace(/<[^>]+>/g, ""),
23284
+ { ...threadOpts, ...replyOpts }
23285
+ )
23189
23286
  );
23190
23287
  this.trackAgentMessage(sent.message_id, chatId);
23191
23288
  }
23192
23289
  }
23193
23290
  }
23194
23291
  async sendVoice(chatId, audioBuffer, fileName) {
23195
- await this.bot.api.sendVoice(
23196
- numericChatId(chatId),
23197
- new InputFile(audioBuffer, fileName ?? "response.ogg")
23292
+ await withRetry(
23293
+ "sendVoice",
23294
+ () => this.bot.api.sendVoice(
23295
+ numericChatId(chatId),
23296
+ new InputFile(audioBuffer, fileName ?? "response.ogg")
23297
+ )
23198
23298
  );
23199
23299
  }
23200
23300
  async sendFile(chatId, buffer, fileName) {
23201
- await this.bot.api.sendDocument(
23202
- numericChatId(chatId),
23203
- new InputFile(buffer, fileName)
23301
+ await withRetry(
23302
+ "sendFile",
23303
+ () => this.bot.api.sendDocument(
23304
+ numericChatId(chatId),
23305
+ new InputFile(buffer, fileName)
23306
+ )
23204
23307
  );
23205
23308
  }
23206
23309
  async downloadFile(fileId) {
@@ -23213,7 +23316,10 @@ var init_telegram2 = __esm({
23213
23316
  try {
23214
23317
  const formatted = parseMode === "html" ? text : parseMode === "plain" ? text : formatForTelegram(text);
23215
23318
  const opts = parseMode === "plain" ? {} : { parse_mode: "HTML" };
23216
- const msg = await this.bot.api.sendMessage(numericChatId(chatId), formatted, opts);
23319
+ const msg = await withRetry(
23320
+ "sendTextReturningId",
23321
+ () => this.bot.api.sendMessage(numericChatId(chatId), formatted, opts)
23322
+ );
23217
23323
  return msg.message_id.toString();
23218
23324
  } catch {
23219
23325
  return void 0;
@@ -23222,16 +23328,22 @@ var init_telegram2 = __esm({
23222
23328
  async editText(chatId, messageId, text, parseMode) {
23223
23329
  const formatted = parseMode === "html" ? text : formatForTelegram(text);
23224
23330
  try {
23225
- await this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
23226
- parse_mode: "HTML"
23227
- });
23331
+ await withRetry(
23332
+ "editText:html",
23333
+ () => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
23334
+ parse_mode: "HTML"
23335
+ })
23336
+ );
23228
23337
  return true;
23229
23338
  } catch {
23230
23339
  try {
23231
- await this.bot.api.editMessageText(
23232
- numericChatId(chatId),
23233
- parseInt(messageId),
23234
- formatted.replace(/<[^>]+>/g, "")
23340
+ await withRetry(
23341
+ "editText:fallback",
23342
+ () => this.bot.api.editMessageText(
23343
+ numericChatId(chatId),
23344
+ parseInt(messageId),
23345
+ formatted.replace(/<[^>]+>/g, "")
23346
+ )
23235
23347
  );
23236
23348
  return true;
23237
23349
  } catch {
@@ -23261,16 +23373,22 @@ var init_telegram2 = __esm({
23261
23373
  const MAX_KEYBOARD_TEXT = 4e3;
23262
23374
  const safeText = text.length > MAX_KEYBOARD_TEXT ? text.slice(0, MAX_KEYBOARD_TEXT) + "\n\n\u2026(truncated)" : text;
23263
23375
  try {
23264
- const msg = await this.bot.api.sendMessage(numericChatId(chatId), safeText, {
23265
- reply_markup: keyboard
23266
- });
23376
+ const msg = await withRetry(
23377
+ "sendKeyboard",
23378
+ () => this.bot.api.sendMessage(numericChatId(chatId), safeText, {
23379
+ reply_markup: keyboard
23380
+ })
23381
+ );
23267
23382
  return msg.message_id.toString();
23268
23383
  } catch (err) {
23269
23384
  error(`[telegram] sendKeyboard failed (chat=${chatId}, textLen=${text.length}):`, err);
23270
23385
  try {
23271
- const fallbackMsg = await this.bot.api.sendMessage(numericChatId(chatId), "\u2B06\uFE0F (see above for details)", {
23272
- reply_markup: keyboard
23273
- });
23386
+ const fallbackMsg = await withRetry(
23387
+ "sendKeyboard:fallback",
23388
+ () => this.bot.api.sendMessage(numericChatId(chatId), "\u2B06\uFE0F (see above for details)", {
23389
+ reply_markup: keyboard
23390
+ })
23391
+ );
23274
23392
  return fallbackMsg.message_id.toString();
23275
23393
  } catch (fallbackErr) {
23276
23394
  error(`[telegram] sendKeyboard fallback also failed:`, fallbackErr);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.18.3",
3
+ "version": "0.18.4",
4
4
  "description": "CC-Claw: Personal AI assistant on Telegram — multi-backend (Claude, Gemini, Codex, Cursor), sub-agent orchestration, MCP management",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",