cc-claw 0.29.2 → 0.29.3

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 +253 -75
  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.29.2" : (() => {
36
+ VERSION = true ? "0.29.3" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -2813,6 +2813,7 @@ __export(chat_settings_exports, {
2813
2813
  GLOBAL_SUMMARIZER_SENTINEL: () => GLOBAL_SUMMARIZER_SENTINEL,
2814
2814
  clearAgentMode: () => clearAgentMode,
2815
2815
  clearAllPaidSlots: () => clearAllPaidSlots,
2816
+ clearAllSummarizerOverrides: () => clearAllSummarizerOverrides,
2816
2817
  clearChatPaidSlots: () => clearChatPaidSlots,
2817
2818
  clearCwd: () => clearCwd,
2818
2819
  clearExecMode: () => clearExecMode,
@@ -2820,6 +2821,7 @@ __export(chat_settings_exports, {
2820
2821
  clearModelMap: () => clearModelMap,
2821
2822
  clearSummarizer: () => clearSummarizer,
2822
2823
  clearThinkingLevel: () => clearThinkingLevel,
2824
+ countSummarizerOverrides: () => countSummarizerOverrides,
2823
2825
  deleteBookmark: () => deleteBookmark,
2824
2826
  determineEscalationTarget: () => determineEscalationTarget,
2825
2827
  findBookmarksByPrefix: () => findBookmarksByPrefix,
@@ -2840,6 +2842,7 @@ __export(chat_settings_exports, {
2840
2842
  getShowThinkingUi: () => getShowThinkingUi,
2841
2843
  getSkillSuggestionsEnabled: () => getSkillSuggestionsEnabled,
2842
2844
  getSummarizer: () => getSummarizer,
2845
+ getSummarizerWithSource: () => getSummarizerWithSource,
2843
2846
  getThinkingLevel: () => getThinkingLevel,
2844
2847
  getToolsMap: () => getToolsMap,
2845
2848
  getVerboseLevel: () => getVerboseLevel,
@@ -3184,6 +3187,35 @@ function setSummarizer(chatId, backend2, model2) {
3184
3187
  function clearSummarizer(chatId) {
3185
3188
  getDb().prepare("DELETE FROM chat_summarizer WHERE chat_id = ?").run(chatId);
3186
3189
  }
3190
+ function clearAllSummarizerOverrides() {
3191
+ const result = getDb().prepare(
3192
+ "DELETE FROM chat_summarizer WHERE chat_id != ?"
3193
+ ).run(GLOBAL_SUMMARIZER_SENTINEL);
3194
+ return result.changes;
3195
+ }
3196
+ function getSummarizerWithSource(chatId) {
3197
+ const perChat = getDb().prepare(
3198
+ "SELECT backend, model FROM chat_summarizer WHERE chat_id = ?"
3199
+ ).get(chatId);
3200
+ const globalRow = getDb().prepare(
3201
+ "SELECT backend, model FROM chat_summarizer WHERE chat_id = ?"
3202
+ ).get(GLOBAL_SUMMARIZER_SENTINEL);
3203
+ const hasPerChat = perChat && (perChat.backend || perChat.model);
3204
+ const globalConfig = globalRow && (globalRow.backend || globalRow.model) ? globalRow : { backend: null, model: null };
3205
+ if (hasPerChat) {
3206
+ return { config: perChat, source: "per-chat", globalConfig };
3207
+ }
3208
+ if (globalConfig.backend || globalConfig.model) {
3209
+ return { config: globalConfig, source: "global", globalConfig };
3210
+ }
3211
+ return { config: { backend: null, model: null }, source: "auto", globalConfig };
3212
+ }
3213
+ function countSummarizerOverrides() {
3214
+ const row = getDb().prepare(
3215
+ "SELECT COUNT(*) as cnt FROM chat_summarizer WHERE chat_id != ?"
3216
+ ).get(GLOBAL_SUMMARIZER_SENTINEL);
3217
+ return row.cnt;
3218
+ }
3187
3219
  function getAgentMode(chatId) {
3188
3220
  const row = getDb().prepare("SELECT mode FROM chat_agent_mode WHERE chat_id = ?").get(chatId);
3189
3221
  return row?.mode ?? "auto";
@@ -3325,7 +3357,7 @@ function getUsage(chatId) {
3325
3357
  }
3326
3358
  function addUsage(chatId, input, output2, cacheRead, model2, backend2, contextSize) {
3327
3359
  const db3 = getDb();
3328
- const finalContextSize = contextSize ?? input + cacheRead;
3360
+ const finalContextSize = contextSize === null ? 0 : contextSize ?? input + cacheRead;
3329
3361
  db3.prepare(`
3330
3362
  INSERT INTO chat_usage (chat_id, input_tokens, output_tokens, cache_read_tokens, request_count, last_input_tokens, last_cache_read_tokens, context_size, updated_at)
3331
3363
  VALUES (?, ?, ?, ?, 1, ?, ?, ?, datetime('now'))
@@ -4593,6 +4625,12 @@ var init_session_log = __esm({
4593
4625
  });
4594
4626
 
4595
4627
  // src/memory/api-context.ts
4628
+ var api_context_exports = {};
4629
+ __export(api_context_exports, {
4630
+ buildApiMessages: () => buildApiMessages,
4631
+ estimateContextUsage: () => estimateContextUsage,
4632
+ estimateTokens: () => estimateTokens
4633
+ });
4596
4634
  import { getEncoding } from "js-tiktoken";
4597
4635
  function estimateTokens(text) {
4598
4636
  return enc.encode(text).length;
@@ -4621,7 +4659,7 @@ async function buildApiMessages(chatId, userMessage, systemPrompt, contextWindow
4621
4659
  return { role: "assistant", content: entry.text };
4622
4660
  });
4623
4661
  const currentUserMessage = { role: "user", content: userMessage };
4624
- const tokenBudget = Math.floor(contextWindow * 0.85);
4662
+ const tokenBudget = Math.floor(contextWindow * 0.95);
4625
4663
  const fixedMessages = [systemMessage, currentUserMessage];
4626
4664
  const fixedTokens = fixedMessages.reduce((sum, m) => sum + enc.encode(typeof m.content === "string" ? m.content : JSON.stringify(m.content)).length, 0);
4627
4665
  const historyBudget = tokenBudget - fixedTokens;
@@ -4637,12 +4675,30 @@ async function buildApiMessages(chatId, userMessage, systemPrompt, contextWindow
4637
4675
  }
4638
4676
  return [systemMessage, ...truncatedHistory, currentUserMessage];
4639
4677
  }
4640
- var enc;
4678
+ function estimateContextUsage(chatId, contextWindow) {
4679
+ const logEntries = getLog(chatId);
4680
+ if (logEntries.length === 0) {
4681
+ estimateCache.delete(chatId);
4682
+ return { estimatedTokens: 0, contextWindow, percentage: 0 };
4683
+ }
4684
+ const cached = estimateCache.get(chatId);
4685
+ if (cached && cached.logSize === logEntries.length) {
4686
+ const percentage2 = contextWindow > 0 ? cached.tokens / contextWindow * 100 : 0;
4687
+ return { estimatedTokens: cached.tokens, contextWindow, percentage: percentage2 };
4688
+ }
4689
+ const text = logEntries.map((e) => e.text).join("\n");
4690
+ const estimatedTokens = estimateTokens(text);
4691
+ estimateCache.set(chatId, { logSize: logEntries.length, tokens: estimatedTokens });
4692
+ const percentage = contextWindow > 0 ? estimatedTokens / contextWindow * 100 : 0;
4693
+ return { estimatedTokens, contextWindow, percentage };
4694
+ }
4695
+ var enc, estimateCache;
4641
4696
  var init_api_context = __esm({
4642
4697
  "src/memory/api-context.ts"() {
4643
4698
  "use strict";
4644
4699
  init_session_log();
4645
4700
  enc = getEncoding("cl100k_base");
4701
+ estimateCache = /* @__PURE__ */ new Map();
4646
4702
  }
4647
4703
  });
4648
4704
 
@@ -4838,6 +4894,7 @@ __export(store_exports5, {
4838
4894
  clearAgentMode: () => clearAgentMode,
4839
4895
  clearAllPaidSlots: () => clearAllPaidSlots,
4840
4896
  clearAllSessions: () => clearAllSessions,
4897
+ clearAllSummarizerOverrides: () => clearAllSummarizerOverrides,
4841
4898
  clearBackendLimit: () => clearBackendLimit,
4842
4899
  clearChatBackendSlot: () => clearChatBackendSlot,
4843
4900
  clearChatGeminiSlot: () => clearChatGeminiSlot,
@@ -4852,6 +4909,7 @@ __export(store_exports5, {
4852
4909
  clearThinkingLevel: () => clearThinkingLevel,
4853
4910
  clearUsage: () => clearUsage,
4854
4911
  completeJobRun: () => completeJobRun,
4912
+ countSummarizerOverrides: () => countSummarizerOverrides,
4855
4913
  deleteBookmark: () => deleteBookmark,
4856
4914
  deleteMemoryById: () => deleteMemoryById,
4857
4915
  deleteSessionSummary: () => deleteSessionSummary,
@@ -4923,6 +4981,7 @@ __export(store_exports5, {
4923
4981
  getShowThinkingUi: () => getShowThinkingUi,
4924
4982
  getSkillSuggestionsEnabled: () => getSkillSuggestionsEnabled,
4925
4983
  getSummarizer: () => getSummarizer,
4984
+ getSummarizerWithSource: () => getSummarizerWithSource,
4926
4985
  getThinkingLevel: () => getThinkingLevel,
4927
4986
  getToolsMap: () => getToolsMap,
4928
4987
  getUnsummarizedChatIds: () => getUnsummarizedChatIds,
@@ -7581,13 +7640,14 @@ function is429(err) {
7581
7640
  function sleep(ms) {
7582
7641
  return new Promise((r) => setTimeout(r, ms));
7583
7642
  }
7584
- var PER_DM_INTERVAL_MS, PER_GROUP_INTERVAL_MS, GLOBAL_INTERVAL_MS, MAX_RETRIES2, RETRY_DELAY_MS, MAX_QUEUE_SIZE, EDIT_PRESSURE_THRESHOLD, MAX_PER_CHAT_QUEUE, MAX_TOTAL_PAUSE_MS, CIRCUIT_TRIP_THRESHOLD, CIRCUIT_TRIP_WINDOW_MS, CIRCUIT_COOLDOWN_STEP_SEC, CIRCUIT_RESET_WINDOW_MS, CircuitState, Priority, _activeThrottle, TelegramThrottle;
7643
+ var PER_DM_INTERVAL_MS, PER_GROUP_INTERVAL_MS, P0_PACING_MS, GLOBAL_INTERVAL_MS, MAX_RETRIES2, RETRY_DELAY_MS, MAX_QUEUE_SIZE, EDIT_PRESSURE_THRESHOLD, MAX_PER_CHAT_QUEUE, MAX_TOTAL_PAUSE_MS, CIRCUIT_TRIP_THRESHOLD, CIRCUIT_TRIP_WINDOW_MS, CIRCUIT_COOLDOWN_STEP_SEC, CIRCUIT_RESET_WINDOW_MS, CircuitState, Priority, _activeThrottle, TelegramThrottle;
7585
7644
  var init_telegram_throttle = __esm({
7586
7645
  "src/channels/telegram-throttle.ts"() {
7587
7646
  "use strict";
7588
7647
  init_log();
7589
7648
  PER_DM_INTERVAL_MS = 1e3;
7590
7649
  PER_GROUP_INTERVAL_MS = 3500;
7650
+ P0_PACING_MS = 150;
7591
7651
  GLOBAL_INTERVAL_MS = 100;
7592
7652
  MAX_RETRIES2 = 2;
7593
7653
  RETRY_DELAY_MS = 1e3;
@@ -7766,16 +7826,17 @@ var init_telegram_throttle = __esm({
7766
7826
  while (this.queue.length > 0) {
7767
7827
  while (this.isPaused()) {
7768
7828
  if (this.pauseStartedAt > 0 && Date.now() - this.pauseStartedAt > MAX_TOTAL_PAUSE_MS) {
7769
- warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items`);
7829
+ warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items (pause remains until ${new Date(this.pausedUntil).toISOString()})`);
7770
7830
  this.flushQueueWithError("Telegram rate limit exceeded max wait time");
7771
- this.pausedUntil = 0;
7772
- this.pauseStartedAt = 0;
7773
7831
  break;
7774
7832
  }
7775
7833
  const waitMs = Math.min(this.pausedUntil - Date.now(), 5e3);
7776
7834
  if (waitMs > 0) await sleep(waitMs);
7777
7835
  }
7778
7836
  if (this.queue.length === 0) break;
7837
+ if (!this.isPaused() && this.pauseStartedAt > 0) {
7838
+ this.pauseStartedAt = 0;
7839
+ }
7779
7840
  this.updateCircuitState();
7780
7841
  const item = this.selectNextItem();
7781
7842
  if (!item) {
@@ -7783,7 +7844,8 @@ var init_telegram_throttle = __esm({
7783
7844
  continue;
7784
7845
  }
7785
7846
  const lastChat = this.lastSendPerChat.get(item.chatId) ?? 0;
7786
- const chatWait = perChatInterval(item.chatId) - (Date.now() - lastChat);
7847
+ const interval = item.priority === 0 /* P0_CRITICAL */ ? P0_PACING_MS : perChatInterval(item.chatId);
7848
+ const chatWait = interval - (Date.now() - lastChat);
7787
7849
  if (chatWait > 0) await sleep(chatWait);
7788
7850
  const globalWait = GLOBAL_INTERVAL_MS - (Date.now() - this.lastGlobalSend);
7789
7851
  if (globalWait > 0) await sleep(globalWait);
@@ -7865,6 +7927,7 @@ var init_telegram_throttle = __esm({
7865
7927
  return await fn();
7866
7928
  } catch (err) {
7867
7929
  if (is429(err)) throw err;
7930
+ if (err instanceof GrammyError && err.error_code >= 400 && err.error_code < 500) throw err;
7868
7931
  if (attempt < MAX_RETRIES2 && err instanceof GrammyError) {
7869
7932
  warn(`[throttle] ${label2} attempt ${attempt + 1}/${MAX_RETRIES2} failed (${err.error_code}), retrying`);
7870
7933
  await sleep(RETRY_DELAY_MS);
@@ -13382,7 +13445,7 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
13382
13445
  const cap = getOllamaTranscriptCap(ollamaModel);
13383
13446
  const key = `${ollamaAdapter.id}:${ollamaModel}`;
13384
13447
  tried.add(key);
13385
- const directFn = (prompt) => ollamaAdapter.streamDirect(prompt, ollamaModel);
13448
+ const directFn = (prompt) => ollamaAdapter.streamDirect(prompt, ollamaModel, { thinkingLevel: "off" });
13386
13449
  const result = await attemptSummarizeDirect(chatId, directFn, "ollama", ollamaModel, entries, cap);
13387
13450
  if (result.success) {
13388
13451
  await extractAndLogSignals(result.rawText, chatId, "ollama", ollamaModel);
@@ -13400,7 +13463,7 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
13400
13463
  const key = `${targetAdapter.id}:${model2}`;
13401
13464
  if (!tried.has(key)) {
13402
13465
  tried.add(key);
13403
- const result = targetAdapter.streamDirect ? await attemptSummarizeDirect(chatId, (p) => targetAdapter.streamDirect(p, model2), targetAdapter.id, model2, entries, getTranscriptCap(model2)) : await attemptSummarize(chatId, targetAdapter, model2, entries);
13466
+ const result = targetAdapter.streamDirect ? await attemptSummarizeDirect(chatId, (p) => targetAdapter.streamDirect(p, model2, { thinkingLevel: "off" }), targetAdapter.id, model2, entries, getTranscriptCap(model2)) : await attemptSummarize(chatId, targetAdapter, model2, entries);
13404
13467
  if (result.success) {
13405
13468
  await extractAndLogSignals(result.rawText, chatId, targetAdapter.id, model2);
13406
13469
  if (clearLogAfter) clearLog(chatId);
@@ -13418,7 +13481,7 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
13418
13481
  const key = `${adapter.id}:${model2}`;
13419
13482
  if (!tried.has(key)) {
13420
13483
  tried.add(key);
13421
- const result = adapter.streamDirect ? await attemptSummarizeDirect(chatId, (p) => adapter.streamDirect(p, model2), adapter.id, model2, entries, adapter.id === "ollama" ? getOllamaTranscriptCap(model2) : getTranscriptCap(model2)) : await attemptSummarize(chatId, adapter, model2, entries);
13484
+ const result = adapter.streamDirect ? await attemptSummarizeDirect(chatId, (p) => adapter.streamDirect(p, model2, { thinkingLevel: "off" }), adapter.id, model2, entries, adapter.id === "ollama" ? getOllamaTranscriptCap(model2) : getTranscriptCap(model2)) : await attemptSummarize(chatId, adapter, model2, entries);
13422
13485
  if (result.success) {
13423
13486
  await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
13424
13487
  if (clearLogAfter) clearLog(chatId);
@@ -13440,7 +13503,7 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
13440
13503
  let result;
13441
13504
  if (adapter.streamDirect) {
13442
13505
  const cap = adapter.id === "ollama" ? getOllamaTranscriptCap(model2) : getTranscriptCap(model2);
13443
- const directFn = (prompt) => adapter.streamDirect(prompt, model2);
13506
+ const directFn = (prompt) => adapter.streamDirect(prompt, model2, { thinkingLevel: "off" });
13444
13507
  result = await attemptSummarizeDirect(chatId, directFn, adapter.id, model2, entries, cap);
13445
13508
  } else {
13446
13509
  result = await attemptSummarize(chatId, adapter, model2, entries);
@@ -13953,6 +14016,17 @@ function killProcessGroup(proc, signal = "SIGTERM") {
13953
14016
  }
13954
14017
  }
13955
14018
  }
14019
+ function runCompaction(chatId, reason, onCompaction) {
14020
+ return summarizeWithFallbackChain(chatId).then((saved) => {
14021
+ if (saved) {
14022
+ clearSession(chatId);
14023
+ clearUsage(chatId);
14024
+ onCompaction?.(chatId);
14025
+ }
14026
+ }).catch((err) => {
14027
+ warn(`[agent] Compaction failed for ${chatId} (${reason}): ${err}`);
14028
+ });
14029
+ }
13956
14030
  function sweepStaleChatEntries() {
13957
14031
  for (const [chatId, state] of activeChats) {
13958
14032
  if (state.process && state.process.exitCode !== null) {
@@ -14228,7 +14302,6 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14228
14302
  input += ev.usage.input;
14229
14303
  output2 += ev.usage.output;
14230
14304
  cacheRead += ev.usage.cacheRead;
14231
- contextSize = ev.usage.input + (ev.usage.cacheRead ?? 0);
14232
14305
  }
14233
14306
  break;
14234
14307
  case "result":
@@ -14246,7 +14319,6 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14246
14319
  input = ev.usage.input;
14247
14320
  output2 = ev.usage.output;
14248
14321
  cacheRead = ev.usage.cacheRead;
14249
- contextSize = ev.usage.input + (ev.usage.cacheRead ?? 0);
14250
14322
  }
14251
14323
  if (adapter.shouldKillOnResult()) {
14252
14324
  try {
@@ -14329,7 +14401,7 @@ Partial output: ${accumulatedText.slice(-500)}`;
14329
14401
  return;
14330
14402
  }
14331
14403
  const cleanedResult = stripThinkingContent(resultText || accumulatedText);
14332
- resolve3({ resultText: cleanedResult, thinkingText: accumulatedThinking, sessionId, input, output: output2, cacheRead, contextSize, sawToolEvents, sawResultEvent });
14404
+ resolve3({ resultText: cleanedResult, thinkingText: accumulatedThinking, sessionId, input, output: output2, cacheRead, contextSize: null, sawToolEvents, sawResultEvent });
14333
14405
  });
14334
14406
  });
14335
14407
  }
@@ -14548,9 +14620,12 @@ async function askAgentImpl(chatId, userMessage, opts) {
14548
14620
  activeChats.set(chatId, cancelState2);
14549
14621
  try {
14550
14622
  let messageHistory;
14623
+ let apiContextSize;
14551
14624
  if (adapter.type === "api") {
14552
14625
  const contextWindow = adapter.contextWindow[resolvedModel2] ?? 8192;
14553
- messageHistory = await buildApiMessages(chatId, userMessage, fullPrompt, contextWindow);
14626
+ const { buildApiMessages: buildMsgs, estimateContextUsage: estimateContextUsage2 } = await Promise.resolve().then(() => (init_api_context(), api_context_exports));
14627
+ messageHistory = await buildMsgs(chatId, userMessage, fullPrompt, contextWindow);
14628
+ apiContextSize = estimateContextUsage2(chatId, contextWindow).estimatedTokens;
14554
14629
  }
14555
14630
  const sdResult = await adapter.streamDirect(fullPrompt, resolvedModel2, {
14556
14631
  timeoutMs: timeoutMs ?? 3e5,
@@ -14566,23 +14641,29 @@ async function askAgentImpl(chatId, userMessage, opts) {
14566
14641
  });
14567
14642
  if (!isSyntheticChatId(chatId)) {
14568
14643
  appendToLog(chatId, userMessage, sdResult.text, adapter.id, resolvedModel2, null);
14569
- const AUTO_SUMMARIZE_THRESHOLD = 30;
14570
- const pairCount = profile !== "chat" ? getMessagePairCount(chatId) : 0;
14571
- if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
14572
- log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
14573
- summarizeWithFallbackChain(chatId).then((saved) => {
14574
- if (saved) {
14575
- clearSession(chatId);
14576
- opts?.onCompaction?.(chatId);
14577
- }
14578
- }).catch((err) => {
14579
- warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
14580
- });
14644
+ if (apiContextSize && adapter.type === "api" && !compactionInFlight.has(chatId)) {
14645
+ const contextWindow = adapter.contextWindow[resolvedModel2] ?? 8192;
14646
+ const contextPct = apiContextSize / contextWindow * 100;
14647
+ if (contextPct >= 85) {
14648
+ compactionInFlight.add(chatId);
14649
+ log(`[agent] Context at ${contextPct.toFixed(0)}% for ${chatId} \u2014 triggering background compaction`);
14650
+ opts?.onCompaction?.(chatId, "triggered");
14651
+ runCompaction(chatId, "context-85%", opts?.onCompaction).finally(() => {
14652
+ compactionInFlight.delete(chatId);
14653
+ });
14654
+ }
14655
+ }
14656
+ if (adapter.type !== "api" || !compactionInFlight.has(chatId)) {
14657
+ const pairCount = profile !== "chat" ? getMessagePairCount(chatId) : 0;
14658
+ if (pairCount >= 30) {
14659
+ log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
14660
+ runCompaction(chatId, "30-pair-threshold", opts?.onCompaction);
14661
+ }
14581
14662
  }
14582
14663
  }
14583
14664
  const sdUsage = sdResult.usage ?? { input: 0, output: 0 };
14584
14665
  if (sdUsage.input + sdUsage.output > 0) {
14585
- addUsage(chatId, sdUsage.input, sdUsage.output, 0, resolvedModel2);
14666
+ addUsage(chatId, sdUsage.input, sdUsage.output, 0, resolvedModel2, adapter.id, apiContextSize);
14586
14667
  }
14587
14668
  if (cancelState2.cancelled) {
14588
14669
  return { text: "Stopped.", usage: { input: sdUsage.input, output: sdUsage.output, cacheRead: 0 } };
@@ -14884,18 +14965,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
14884
14965
  }
14885
14966
  if (result.resultText && !isSyntheticChatId(chatId)) {
14886
14967
  appendToLog(chatId, userMessage, result.resultText, adapter.id, model2 ?? null, result.sessionId ?? null);
14887
- const AUTO_SUMMARIZE_THRESHOLD = 30;
14888
14968
  const pairCount = profile !== "chat" ? getMessagePairCount(chatId) : 0;
14889
- if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
14969
+ if (pairCount >= 30) {
14890
14970
  log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
14891
- summarizeWithFallbackChain(chatId).then((saved) => {
14892
- if (saved) {
14893
- clearSession(chatId);
14894
- opts?.onCompaction?.(chatId);
14895
- }
14896
- }).catch((err) => {
14897
- warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
14898
- });
14971
+ runCompaction(chatId, "30-pair-threshold", opts?.onCompaction);
14899
14972
  }
14900
14973
  }
14901
14974
  return {
@@ -14923,7 +14996,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
14923
14996
  if (!flag) return args;
14924
14997
  return [...args, ...flag, mcpConfigPath, "--strict-mcp-config"];
14925
14998
  }
14926
- var activeChats, staleSweepTimer, 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;
14999
+ var activeChats, compactionInFlight, staleSweepTimer, 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;
14927
15000
  var init_agent = __esm({
14928
15001
  "src/agent.ts"() {
14929
15002
  "use strict";
@@ -14940,7 +15013,6 @@ var init_agent = __esm({
14940
15013
  init_strip_thinking();
14941
15014
  init_text_utils();
14942
15015
  init_session_log();
14943
- init_api_context();
14944
15016
  init_summarize();
14945
15017
  init_quota();
14946
15018
  init_store5();
@@ -14951,6 +15023,7 @@ var init_agent = __esm({
14951
15023
  init_unified_config();
14952
15024
  init_mcp_config();
14953
15025
  activeChats = /* @__PURE__ */ new Map();
15026
+ compactionInFlight = /* @__PURE__ */ new Set();
14954
15027
  chatLocks = /* @__PURE__ */ new Map();
14955
15028
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
14956
15029
  FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
@@ -15118,7 +15191,9 @@ async function runWeeklySweep(chatId, channel, backendId, model2) {
15118
15191
  buttons.push([{ label: "Review Now", data: "mem:opt:start", style: "success" }]);
15119
15192
  }
15120
15193
  buttons.push([{ label: "Dismiss", data: "mem:sweep:dismiss" }]);
15121
- await sendOrEditKeyboard(chatId, channel, void 0, lines.join("\n"), buttons);
15194
+ if (channel) {
15195
+ await sendOrEditKeyboard(chatId, channel, void 0, lines.join("\n"), buttons);
15196
+ }
15122
15197
  return { suggestionsCount, cleanedUp };
15123
15198
  } catch (err) {
15124
15199
  const msg = errorMessage(err);
@@ -22603,6 +22678,7 @@ var init_ollama2 = __esm({
22603
22678
  async streamDirect(prompt, model2, opts) {
22604
22679
  const cleanPrompt = stripForLocalModel(prompt);
22605
22680
  let disableThinking = false;
22681
+ let modelContextWindow;
22606
22682
  try {
22607
22683
  const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
22608
22684
  const modelRecord = OllamaStore.getModelByName(model2);
@@ -22611,8 +22687,14 @@ var init_ollama2 = __esm({
22611
22687
  } else if (opts?.thinkingLevel === "off") {
22612
22688
  disableThinking = true;
22613
22689
  }
22690
+ if (modelRecord?.contextWindow && modelRecord.contextWindow > 4096) {
22691
+ modelContextWindow = modelRecord.contextWindow;
22692
+ }
22614
22693
  } catch {
22615
22694
  }
22695
+ const ollamaProviderOpts = {};
22696
+ if (disableThinking) ollamaProviderOpts.think = false;
22697
+ if (modelContextWindow) ollamaProviderOpts.num_ctx = modelContextWindow;
22616
22698
  const apiOpts = {
22617
22699
  timeoutMs: opts?.timeoutMs,
22618
22700
  onStream: opts?.onStream,
@@ -22621,7 +22703,7 @@ var init_ollama2 = __esm({
22621
22703
  permMode: opts?.permMode,
22622
22704
  thinkingLevel: opts?.thinkingLevel,
22623
22705
  onThinking: opts?.onThinking,
22624
- ...disableThinking ? { providerOptions: { ollama: { think: false } } } : {}
22706
+ ...Object.keys(ollamaProviderOpts).length > 0 ? { providerOptions: { ollama: ollamaProviderOpts } } : {}
22625
22707
  };
22626
22708
  const result = await this.streamDirectWithHistory(
22627
22709
  cleanPrompt,
@@ -22707,7 +22789,28 @@ var init_openrouter = __esm({
22707
22789
  }
22708
22790
  summarizerModel = DEFAULT_FREE_MODEL;
22709
22791
  pricing = {};
22710
- contextWindow = {};
22792
+ _contextWindowCache = null;
22793
+ _contextWindowCacheSize = 0;
22794
+ get contextWindow() {
22795
+ try {
22796
+ const { getApiModels: getApiModels2 } = (init_api_models(), __toCommonJS(api_models_exports));
22797
+ const models = getApiModels2("openrouter");
22798
+ if (this._contextWindowCache && models.length === this._contextWindowCacheSize) {
22799
+ return this._contextWindowCache;
22800
+ }
22801
+ const result = {};
22802
+ for (const m of models) {
22803
+ if (m.contextWindow) {
22804
+ result[m.modelId] = m.contextWindow;
22805
+ }
22806
+ }
22807
+ this._contextWindowCache = result;
22808
+ this._contextWindowCacheSize = models.length;
22809
+ return result;
22810
+ } catch {
22811
+ return {};
22812
+ }
22813
+ }
22711
22814
  // ── Vercel AI SDK provider ────────────────────────────────────────
22712
22815
  /**
22713
22816
  * Create the Vercel AI SDK LanguageModel for a given model ID.
@@ -24806,6 +24909,7 @@ var init_live_status = __esm({
24806
24909
  /** Spinner frame counter — advances on each flush for animation. */
24807
24910
  spinnerFrame = 0;
24808
24911
  /** Timestamp of last successful edit — used for heartbeat force-through. */
24912
+ lastSuccessfulFlushAt = 0;
24809
24913
  /** Callback to restart typing indicator as fallback. */
24810
24914
  onTypingFallback;
24811
24915
  /** Set a callback that restarts the typing indicator loop as a fallback. */
@@ -29358,6 +29462,7 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
29358
29462
  const summarized = await summarizeSession(chatId);
29359
29463
  clearSession(chatId);
29360
29464
  clearChatPaidSlots(chatId);
29465
+ clearUsage(chatId);
29361
29466
  setSessionStartedAt(chatId);
29362
29467
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "New session started", detail: { field: "session", action: "reset", summarized } });
29363
29468
  if (typeof channel.sendKeyboard === "function" && oldSessionId) {
@@ -29411,6 +29516,7 @@ async function handleClearCommand(chatId, _commandArgs, _msg, channel) {
29411
29516
  stopAllSideQuests(chatId);
29412
29517
  clearSession(chatId);
29413
29518
  clearChatPaidSlots(chatId);
29519
+ clearUsage(chatId);
29414
29520
  setSessionStartedAt(chatId);
29415
29521
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "Session cleared (no summary)", detail: { field: "session", action: "clear" } });
29416
29522
  await channel.sendText(chatId, "\u{1F9FD} Session cleared. No summary saved.", { parseMode: "plain", priority: 0 /* P0_CRITICAL */ });
@@ -29487,6 +29593,17 @@ async function handleSummarizeCommand(chatId, commandArgs, msg, channel) {
29487
29593
  }
29488
29594
  }
29489
29595
  }
29596
+ function formatSummarizerLabel(backend2, model2, fallbackModel) {
29597
+ if (backend2 === "off") return "Off";
29598
+ if (backend2) return `${backend2}: ${model2 ?? "default"}`;
29599
+ return `Auto (${fallbackModel ?? "default"})`;
29600
+ }
29601
+ function summarizerStatusLine(chatId, adapter) {
29602
+ const { config: config2, source } = getSummarizerWithSource(chatId);
29603
+ const label2 = formatSummarizerLabel(config2.backend, config2.model, adapter?.summarizerModel);
29604
+ if (source === "auto") return label2.toLowerCase();
29605
+ return `${label2} (${source === "global" ? "global" : "per-chat"})`;
29606
+ }
29490
29607
  async function handleStatusCommand(chatId, commandArgs, msg, channel) {
29491
29608
  const sessionId = getSessionId(chatId);
29492
29609
  const cwd = getCwd(chatId);
@@ -29503,12 +29620,21 @@ async function handleStatusCommand(chatId, commandArgs, msg, channel) {
29503
29620
  const thinking2 = getThinkingLevel(chatId);
29504
29621
  const mode = getMode(chatId);
29505
29622
  const modelSig = getModelSignature(chatId);
29506
- const contextMax = adapter?.contextWindow[model2] ?? 2e5;
29507
- const contextUsed = usage2.context_size;
29508
- const contextPct = contextMax > 0 ? contextUsed / contextMax * 100 : 0;
29509
- const ctxBar = buildBar(contextPct);
29510
- const usedK = (contextUsed / 1e3).toFixed(1);
29511
- const maxK = (contextMax / 1e3).toFixed(0);
29623
+ let contextLine;
29624
+ if (adapter?.type === "api") {
29625
+ const { estimateContextUsage: estimateContextUsage2 } = await Promise.resolve().then(() => (init_api_context(), api_context_exports));
29626
+ const contextMax = adapter.contextWindow[model2] ?? 8192;
29627
+ const ctxEst = estimateContextUsage2(chatId, contextMax);
29628
+ const ctxBar = buildBar(ctxEst.percentage);
29629
+ const usedK = (ctxEst.estimatedTokens / 1e3).toFixed(1);
29630
+ const maxK = (contextMax / 1e3).toFixed(0);
29631
+ contextLine = `\u{1F4D0} Context: ${ctxBar} ${usedK}K/${maxK}K (${ctxEst.percentage.toFixed(1)}%) \xB7 compacts at 85%`;
29632
+ } else {
29633
+ const pairCount = getMessagePairCount(chatId);
29634
+ const threshold = 30;
29635
+ const remaining = Math.max(0, threshold - pairCount);
29636
+ contextLine = `\u{1F4D0} Session: ${pairCount}/${threshold} messages \xB7 compacts in ${remaining}`;
29637
+ }
29512
29638
  const bootRow = getDb().prepare("SELECT value FROM meta WHERE key = 'boot_time'").get();
29513
29639
  let uptimeStr = "unknown";
29514
29640
  if (bootRow) {
@@ -29571,11 +29697,12 @@ async function handleStatusCommand(chatId, commandArgs, msg, channel) {
29571
29697
  `\u{1F4AD} Think: ${thinking2} \xB7 Mode: ${mode}`,
29572
29698
  `\u{1F916} Agents: ${getAgentMode(chatId)}`,
29573
29699
  `\u{1F507} Voice: ${voice2 ? "on" : "off"} \xB7 Sig: ${modelSig}`,
29700
+ `\u{1F4DD} Summarizer: ${summarizerStatusLine(chatId, adapter)}`,
29574
29701
  ``,
29575
29702
  buildSectionHeader("Session"),
29576
29703
  `\u{1F4CB} ${sessionId ?? "no active session"}`,
29577
29704
  `\u{1F4C1} ${cwd ?? "default workspace"}`,
29578
- `\u{1F4D0} Context: ${ctxBar} ${usedK}K/${maxK}K (${contextPct.toFixed(1)}%)`,
29705
+ contextLine,
29579
29706
  ...sqCount > 0 ? [`\u{1F5FA} Side quests: ${sqCount} active`] : [],
29580
29707
  ``,
29581
29708
  buildSectionHeader("Usage"),
@@ -29986,8 +30113,17 @@ async function handleSummarizerCommand(chatId, commandArgs, msg, channel) {
29986
30113
  } catch {
29987
30114
  adapter = null;
29988
30115
  }
29989
- const current = getSummarizer(chatId);
29990
- const currentLabel = current.backend === "off" ? "Off" : current.backend ? `${current.backend}:${current.model ?? "default"}` : `Auto (${adapter?.summarizerModel ?? "default"})`;
30116
+ const { config: current, source, globalConfig } = getSummarizerWithSource(chatId);
30117
+ const overrideCount = countSummarizerOverrides();
30118
+ const currentLabel = formatSummarizerLabel(current.backend, current.model, adapter?.summarizerModel);
30119
+ const sourceTag = source === "per-chat" ? "per-chat override" : source === "global" ? "global default" : "auto";
30120
+ const headerLines = [`Summarizer: ${currentLabel} (${sourceTag})`];
30121
+ if (globalConfig.backend) {
30122
+ headerLines.push(`Global default: ${formatSummarizerLabel(globalConfig.backend, globalConfig.model)}`);
30123
+ }
30124
+ if (overrideCount > 0) {
30125
+ headerLines.push(`${overrideCount} chat${overrideCount === 1 ? "" : "s"} with per-chat overrides`);
30126
+ }
29991
30127
  if (typeof channel.sendKeyboard === "function") {
29992
30128
  const isAuto = !current.backend && current.backend !== "off";
29993
30129
  const isOff = current.backend === "off";
@@ -30035,7 +30171,14 @@ async function handleSummarizerCommand(chatId, commandArgs, msg, channel) {
30035
30171
  }]);
30036
30172
  }
30037
30173
  }
30038
- await channel.sendKeyboard(chatId, `Session summarizer (current: ${currentLabel}):`, buttons);
30174
+ if (overrideCount > 0) {
30175
+ buttons.push([{
30176
+ label: `Clear All Overrides (${overrideCount})`,
30177
+ data: "summarizer:clearall",
30178
+ style: "danger"
30179
+ }]);
30180
+ }
30181
+ await channel.sendKeyboard(chatId, headerLines.join("\n"), buttons);
30039
30182
  } else {
30040
30183
  await channel.sendText(chatId, `Summarizer: ${currentLabel}
30041
30184
 
@@ -31558,14 +31701,40 @@ ${value ? "Full tool inputs/results will be saved to ~/.cc-claw/logs/sessions/"
31558
31701
  if (rest === "auto") {
31559
31702
  clearSummarizer(chatId);
31560
31703
  await channel.sendText(chatId, "Summarizer set to auto (uses active backend).", { parseMode: "plain" });
31561
- } else if (rest === "off") {
31562
- setSummarizer(chatId, "off", null);
31563
- await channel.sendText(chatId, "Session summarization disabled.", { parseMode: "plain" });
31704
+ } else if (rest === "clearall") {
31705
+ const cleared = clearAllSummarizerOverrides();
31706
+ await channel.sendText(chatId, `Cleared ${cleared} per-chat override${cleared === 1 ? "" : "s"}. All chats now use the global default.`, { parseMode: "plain" });
31707
+ } else if (rest.startsWith("promote:")) {
31708
+ const promoteValue = rest.slice(8);
31709
+ const db3 = getDb();
31710
+ const promote = db3.transaction(() => {
31711
+ if (promoteValue === "off") {
31712
+ setGlobalSummarizer("off", null);
31713
+ } else {
31714
+ const [bk, ...modelParts] = promoteValue.split(":");
31715
+ setGlobalSummarizer(bk, modelParts.join(":") || null);
31716
+ }
31717
+ return clearAllSummarizerOverrides();
31718
+ });
31719
+ const cleared = promote();
31720
+ const label2 = promoteValue === "off" ? "Off" : promoteValue;
31721
+ const clearedNote = cleared > 0 ? ` Cleared ${cleared} per-chat override${cleared === 1 ? "" : "s"}.` : "";
31722
+ await channel.sendText(chatId, `Global summarizer set to ${label2}.${clearedNote} All chats now use this default.`, { parseMode: "plain" });
31564
31723
  } else {
31565
- const [bk, ...modelParts] = rest.split(":");
31566
- const mdl = modelParts.join(":") || null;
31724
+ const isOff = rest === "off";
31725
+ const bk = isOff ? "off" : rest.split(":")[0];
31726
+ const mdl = isOff ? null : rest.split(":").slice(1).join(":") || null;
31567
31727
  setSummarizer(chatId, bk, mdl);
31568
- await channel.sendText(chatId, `Summarizer pinned to ${bk}:${mdl ?? "default"}.`, { parseMode: "plain" });
31728
+ const displayLabel = isOff ? "Off" : `${bk}:${mdl ?? "default"}`;
31729
+ const confirmMsg = isOff ? "Session summarization disabled for this chat." : `Summarizer pinned to ${displayLabel} for this chat.`;
31730
+ const promoteData = `summarizer:promote:${rest}`;
31731
+ if (typeof channel.sendKeyboard === "function") {
31732
+ await channel.sendKeyboard(chatId, confirmMsg, [
31733
+ [{ label: "Set as Global Default", data: promoteData }]
31734
+ ]);
31735
+ } else {
31736
+ await channel.sendText(chatId, confirmMsg, { parseMode: "plain" });
31737
+ }
31569
31738
  }
31570
31739
  } else if (data.startsWith("perms:")) {
31571
31740
  let chosen = data.slice(6);
@@ -34564,6 +34733,8 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34564
34733
  return;
34565
34734
  }
34566
34735
  getTypingManager().acquire(chatId, channel);
34736
+ let stopDraftTimer = () => {
34737
+ };
34567
34738
  try {
34568
34739
  const tMode = settings.getMode();
34569
34740
  const tVerbose = settings.getVerboseLevel();
@@ -34623,7 +34794,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34623
34794
  draftState.dirty = true;
34624
34795
  };
34625
34796
  }
34626
- const stopDraftTimer2 = () => {
34797
+ stopDraftTimer = () => {
34627
34798
  if (draftState?.flushTimer) {
34628
34799
  clearInterval(draftState.flushTimer);
34629
34800
  draftState.flushTimer = null;
@@ -34659,9 +34830,14 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34659
34830
  } catch {
34660
34831
  }
34661
34832
  },
34662
- onCompaction: (cid) => {
34663
- channel.sendText(cid, "\u{1F4BE} Context saved to memory.").catch(() => {
34664
- });
34833
+ onCompaction: (cid, phase) => {
34834
+ if (phase === "triggered") {
34835
+ channel.sendText(cid, "\u{1F4BE} Context compaction triggered \u2014 saving conversation to memory...").catch(() => {
34836
+ });
34837
+ } else {
34838
+ channel.sendText(cid, "\u{1F4BE} Context saved to memory.").catch(() => {
34839
+ });
34840
+ }
34665
34841
  },
34666
34842
  onSlotRotation: (cid, from, to) => {
34667
34843
  const slots = getGeminiSlots();
@@ -34679,7 +34855,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34679
34855
  });
34680
34856
  }
34681
34857
  });
34682
- stopDraftTimer2();
34858
+ stopDraftTimer();
34683
34859
  const elapsedMs = Date.now() - sigT0;
34684
34860
  const elapsedSec = (elapsedMs / 1e3).toFixed(1);
34685
34861
  if (liveStatus && response.thinkingText?.trim()) {
@@ -36269,7 +36445,7 @@ var init_telegram2 = __esm({
36269
36445
  };
36270
36446
  }
36271
36447
  async start(handler) {
36272
- await this.bot.api.setMyCommands([
36448
+ await this.throttle.send(this.primaryChatId, "setMyCommands", () => this.bot.api.setMyCommands([
36273
36449
  // Core
36274
36450
  { command: "menu", description: "Home screen \u2014 quick-access keyboard" },
36275
36451
  { command: "m", description: "Home screen (alias for /menu)" },
@@ -36347,7 +36523,7 @@ var init_telegram2 = __esm({
36347
36523
  // Context & info
36348
36524
  { command: "info", description: "Current chat context (ID, topic, sender, settings)" },
36349
36525
  { command: "council", description: "Multi-model debate (select models, anonymous rounds)" }
36350
- ]);
36526
+ ]));
36351
36527
  this.bot.on("message", async (ctx) => {
36352
36528
  const chatId = ctx.chat.id.toString();
36353
36529
  const senderId = ctx.from?.id?.toString() ?? "";
@@ -36393,15 +36569,13 @@ var init_telegram2 = __esm({
36393
36569
  const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
36394
36570
  log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
36395
36571
  if (!this.isAuthorized(userId) && !this.isAuthorized(chatId)) {
36396
- ctx.answerCallbackQuery("Unauthorized").catch(() => {
36397
- });
36572
+ this.throttle.tryBestEffort(chatId, "answerCallbackQuery:unauth", () => ctx.answerCallbackQuery("Unauthorized"));
36398
36573
  return;
36399
36574
  }
36400
36575
  const data = ctx.callbackQuery.data;
36401
36576
  const messageId = ctx.callbackQuery.message?.message_id?.toString();
36402
36577
  const threadId = ctx.callbackQuery.message?.message_thread_id;
36403
- ctx.answerCallbackQuery().catch(() => {
36404
- });
36578
+ this.throttle.tryBestEffort(chatId, "answerCallbackQuery", () => ctx.answerCallbackQuery());
36405
36579
  (async () => {
36406
36580
  let ch = this;
36407
36581
  if (threadId) {
@@ -36450,7 +36624,7 @@ var init_telegram2 = __esm({
36450
36624
  this.keepaliveInterval = setInterval(async () => {
36451
36625
  if (!this.pollingExpected) return;
36452
36626
  try {
36453
- await this.bot.api.getMe();
36627
+ await this.throttle.tryBestEffort(this.primaryChatId, "getMe:keepalive", () => this.bot.api.getMe());
36454
36628
  this.lastPollingCheckAt = Date.now();
36455
36629
  } catch (err) {
36456
36630
  error("[telegram] Keepalive ping failed:", err);
@@ -36590,7 +36764,7 @@ var init_telegram2 = __esm({
36590
36764
  );
36591
36765
  }
36592
36766
  async downloadFile(fileId) {
36593
- const file = await this.bot.api.getFile(fileId);
36767
+ const file = await this.throttle.send(this.primaryChatId, "getFile", () => this.bot.api.getFile(fileId));
36594
36768
  const fileUrl = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${file.file_path}`;
36595
36769
  const response = await fetch(fileUrl);
36596
36770
  return Buffer.from(await response.arrayBuffer());
@@ -36879,7 +37053,11 @@ var init_telegram2 = __esm({
36879
37053
  }
36880
37054
  }
36881
37055
  }
36882
- await ctx.answerInlineQuery(results.slice(0, 10), { cache_time: 30 });
37056
+ await this.throttle.tryBestEffort(
37057
+ this.primaryChatId,
37058
+ "answerInlineQuery",
37059
+ () => ctx.answerInlineQuery(results.slice(0, 10), { cache_time: 30 })
37060
+ );
36883
37061
  }
36884
37062
  trackAgentMessage(messageId, chatId) {
36885
37063
  if (this.agentMessageIds.size >= 1e4) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.29.2",
3
+ "version": "0.29.3",
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",