cc-claw 0.29.1 → 0.29.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.
Files changed (2) hide show
  1. package/dist/cli.js +52 -13
  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.1" : (() => {
36
+ VERSION = true ? "0.29.2" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -7593,7 +7593,7 @@ var init_telegram_throttle = __esm({
7593
7593
  RETRY_DELAY_MS = 1e3;
7594
7594
  MAX_QUEUE_SIZE = 60;
7595
7595
  EDIT_PRESSURE_THRESHOLD = MAX_QUEUE_SIZE / 2;
7596
- MAX_PER_CHAT_QUEUE = 15;
7596
+ MAX_PER_CHAT_QUEUE = 30;
7597
7597
  MAX_TOTAL_PAUSE_MS = 5 * 60 * 1e3;
7598
7598
  CIRCUIT_TRIP_THRESHOLD = 3;
7599
7599
  CIRCUIT_TRIP_WINDOW_MS = 5 * 60 * 1e3;
@@ -14088,18 +14088,24 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14088
14088
  let contentSilenceTimer;
14089
14089
  const silenceTimeoutMs = CONTENT_SILENCE_TIMEOUT_MS;
14090
14090
  let silenceResetCount = 0;
14091
- const MAX_SILENCE_RESETS = 20;
14091
+ const MAX_SILENCE_RESETS = 3;
14092
14092
  function resetContentSilenceTimer() {
14093
14093
  if (silenceTimeoutMs <= 0) return;
14094
14094
  if (contentSilenceTimer) clearTimeout(contentSilenceTimer);
14095
14095
  contentSilenceTimer = setTimeout(() => {
14096
14096
  if (cancelState.cancelled || timedOut) return;
14097
14097
  if (pendingTools.size > 0 && silenceResetCount < MAX_SILENCE_RESETS) {
14098
- silenceResetCount++;
14099
- const tools2 = Array.from(pendingTools.values()).map((t) => typeof t === "string" ? t : t.name).join(", ");
14100
- log(`[agent] Content silence timer fired but ${pendingTools.size} tool(s) still running (${tools2}) \u2014 resetting (${silenceResetCount}/${MAX_SILENCE_RESETS})`);
14101
- resetContentSilenceTimer();
14102
- return;
14098
+ const now = Date.now();
14099
+ const hungTool = Array.from(pendingTools.values()).find((t) => now - t.startedAt > silenceTimeoutMs);
14100
+ if (hungTool) {
14101
+ warn(`[agent] Tool "${hungTool.name}" has been running for ${Math.round((now - hungTool.startedAt) / 1e3)}s \u2014 treating as hung, killing`);
14102
+ } else {
14103
+ silenceResetCount++;
14104
+ const tools2 = Array.from(pendingTools.values()).map((t) => `${t.name} (${Math.round((now - t.startedAt) / 1e3)}s)`).join(", ");
14105
+ log(`[agent] Content silence timer fired but ${pendingTools.size} tool(s) still running (${tools2}) \u2014 resetting (${silenceResetCount}/${MAX_SILENCE_RESETS})`);
14106
+ resetContentSilenceTimer();
14107
+ return;
14108
+ }
14103
14109
  }
14104
14110
  warn(`[agent] Content silence timeout after ${silenceTimeoutMs / 1e3}s for ${adapter.id} \u2014 no content events, killing`);
14105
14111
  timedOut = true;
@@ -14178,7 +14184,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14178
14184
  sawToolEvents = true;
14179
14185
  if (opts?.onToolAction && ev.toolName) {
14180
14186
  const toolInput = ev.toolInput ?? {};
14181
- if (ev.toolId) pendingTools.set(ev.toolId, { name: ev.toolName, input: toolInput });
14187
+ if (ev.toolId) pendingTools.set(ev.toolId, { name: ev.toolName, input: toolInput, startedAt: Date.now() });
14182
14188
  opts.onToolAction(ev.toolName, toolInput, void 0, ev.toolId).catch((err) => {
14183
14189
  error("[agent] tool action error:", err);
14184
14190
  });
@@ -24547,7 +24553,7 @@ function getEditCoordinator() {
24547
24553
  function resetEditCoordinator() {
24548
24554
  EditCoordinator.resetInstance();
24549
24555
  }
24550
- var TICK_INTERVAL_MS, MAX_EDITS_PER_WINDOW, EDIT_WINDOW_MS, EditCoordinator;
24556
+ var TICK_INTERVAL_MS, MAX_EDITS_PER_WINDOW, EDIT_WINDOW_MS, MIN_FLUSH_GAP_DM_MS, MIN_FLUSH_GAP_GROUP_MS, EditCoordinator;
24551
24557
  var init_edit_coordinator = __esm({
24552
24558
  "src/channels/edit-coordinator.ts"() {
24553
24559
  "use strict";
@@ -24555,10 +24561,14 @@ var init_edit_coordinator = __esm({
24555
24561
  TICK_INTERVAL_MS = 1e3;
24556
24562
  MAX_EDITS_PER_WINDOW = 4;
24557
24563
  EDIT_WINDOW_MS = 6e4;
24564
+ MIN_FLUSH_GAP_DM_MS = 2e3;
24565
+ MIN_FLUSH_GAP_GROUP_MS = 4e3;
24558
24566
  EditCoordinator = class _EditCoordinator {
24559
24567
  static instance = null;
24560
24568
  /** Active streams indexed by messageId. */
24561
24569
  activeStreams = /* @__PURE__ */ new Map();
24570
+ /** Last flush timestamp per stream — prevents flushing faster than the throttle can drain. */
24571
+ lastFlushAt = /* @__PURE__ */ new Map();
24562
24572
  /** Per-message edit tracking for the sliding window cap. */
24563
24573
  perMessageEditCount = /* @__PURE__ */ new Map();
24564
24574
  /** Single flush timer shared across all streams. */
@@ -24599,6 +24609,7 @@ var init_edit_coordinator = __esm({
24599
24609
  unregister(messageId) {
24600
24610
  this.activeStreams.delete(messageId);
24601
24611
  this.perMessageEditCount.delete(messageId);
24612
+ this.lastFlushAt.delete(messageId);
24602
24613
  this.rebuildKeys();
24603
24614
  log(`[edit-coordinator] unregistered stream ${messageId} (${this.activeStreams.size} remaining)`);
24604
24615
  if (this.activeStreams.size === 0 && this.flushTimer) {
@@ -24616,6 +24627,7 @@ var init_edit_coordinator = __esm({
24616
24627
  }
24617
24628
  this.activeStreams.clear();
24618
24629
  this.perMessageEditCount.clear();
24630
+ this.lastFlushAt.clear();
24619
24631
  this.streamKeys = [];
24620
24632
  this.roundRobinIndex = 0;
24621
24633
  }
@@ -24644,6 +24656,17 @@ var init_edit_coordinator = __esm({
24644
24656
  }
24645
24657
  }
24646
24658
  // ── Internal ──────────────────────────────────────────────────────────
24659
+ /** Check whether enough time has passed since the last flush for this stream.
24660
+ * Group chats need longer gaps (4s) to match the throttle's group pacing (3.5s).
24661
+ * Without this, the coordinator pushes edits faster than the throttle drains them,
24662
+ * causing per-chat queue buildup. */
24663
+ canFlushStream(messageId, stream) {
24664
+ const last = this.lastFlushAt.get(messageId);
24665
+ if (last === void 0) return true;
24666
+ const chatId = stream.getChatId();
24667
+ const minGap = parseInt(chatId) < 0 ? MIN_FLUSH_GAP_GROUP_MS : MIN_FLUSH_GAP_DM_MS;
24668
+ return Date.now() - last >= minGap;
24669
+ }
24647
24670
  /** Rebuild the ordered keys array after registration changes. */
24648
24671
  rebuildKeys() {
24649
24672
  this.streamKeys = Array.from(this.activeStreams.keys());
@@ -24663,11 +24686,12 @@ var init_edit_coordinator = __esm({
24663
24686
  const idx = (startIdx + tried) % this.streamKeys.length;
24664
24687
  const messageId = this.streamKeys[idx];
24665
24688
  const stream = this.activeStreams.get(messageId);
24666
- if (stream && this.canEditMessage(messageId)) {
24689
+ if (stream && this.canEditMessage(messageId) && this.canFlushStream(messageId, stream)) {
24667
24690
  this.roundRobinIndex = (idx + 1) % this.streamKeys.length;
24668
24691
  try {
24669
24692
  await stream.flush();
24670
24693
  this.recordEdit(messageId);
24694
+ this.lastFlushAt.set(messageId, Date.now());
24671
24695
  } catch {
24672
24696
  }
24673
24697
  return;
@@ -25360,10 +25384,12 @@ function getTypingManager() {
25360
25384
  function resetTypingManager() {
25361
25385
  TypingManager.resetInstance();
25362
25386
  }
25363
- var TypingManager;
25387
+ var MAX_TYPING_DURATION_MS, TypingManager;
25364
25388
  var init_typing_manager = __esm({
25365
25389
  "src/channels/typing-manager.ts"() {
25366
25390
  "use strict";
25391
+ init_log();
25392
+ MAX_TYPING_DURATION_MS = 15 * 60 * 1e3;
25367
25393
  TypingManager = class _TypingManager {
25368
25394
  static instance = null;
25369
25395
  activeChats = /* @__PURE__ */ new Map();
@@ -25385,11 +25411,17 @@ var init_typing_manager = __esm({
25385
25411
  }
25386
25412
  channel.sendTyping?.(chatId).catch(() => {
25387
25413
  });
25414
+ const acquiredAt = Date.now();
25388
25415
  const timer = setInterval(() => {
25416
+ if (Date.now() - acquiredAt > MAX_TYPING_DURATION_MS) {
25417
+ warn(`[typing-manager] Auto-releasing chat ${chatId} after ${MAX_TYPING_DURATION_MS / 6e4}min (likely leak)`);
25418
+ this.forceRelease(chatId);
25419
+ return;
25420
+ }
25389
25421
  channel.sendTyping?.(chatId).catch(() => {
25390
25422
  });
25391
25423
  }, 4e3);
25392
- this.activeChats.set(chatId, { refCount: 1, timer });
25424
+ this.activeChats.set(chatId, { refCount: 1, timer, acquiredAt });
25393
25425
  }
25394
25426
  /**
25395
25427
  * Stop showing typing for this agent's perspective.
@@ -25404,6 +25436,13 @@ var init_typing_manager = __esm({
25404
25436
  this.activeChats.delete(chatId);
25405
25437
  }
25406
25438
  }
25439
+ /** Force-release a chat regardless of refCount (for leak recovery). */
25440
+ forceRelease(chatId) {
25441
+ const entry = this.activeChats.get(chatId);
25442
+ if (!entry) return;
25443
+ clearInterval(entry.timer);
25444
+ this.activeChats.delete(chatId);
25445
+ }
25407
25446
  /** Clean shutdown — clear all timers. */
25408
25447
  shutdown() {
25409
25448
  for (const [, entry] of this.activeChats) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.29.1",
3
+ "version": "0.29.2",
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",