cc-claw 0.29.0 → 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 +77 -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.0" : (() => {
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) {
@@ -34568,6 +34607,28 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34568
34607
  }
34569
34608
  };
34570
34609
  }
34610
+ const DRAFT_FLUSH_INTERVAL_MS = 500;
34611
+ const useDraftStreaming = channel.sendMessageDraft && channel.isDraftCapable?.(chatId);
34612
+ const draftState = useDraftStreaming ? { accumulated: "", draftId: Date.now() & 2147483647, dirty: false, flushTimer: null } : null;
34613
+ let onStreamCb;
34614
+ if (draftState) {
34615
+ draftState.flushTimer = setInterval(() => {
34616
+ if (draftState.dirty) {
34617
+ draftState.dirty = false;
34618
+ channel.sendMessageDraft(chatId, draftState.draftId, draftState.accumulated);
34619
+ }
34620
+ }, DRAFT_FLUSH_INTERVAL_MS);
34621
+ onStreamCb = (chunk) => {
34622
+ draftState.accumulated += chunk;
34623
+ draftState.dirty = true;
34624
+ };
34625
+ }
34626
+ const stopDraftTimer2 = () => {
34627
+ if (draftState?.flushTimer) {
34628
+ clearInterval(draftState.flushTimer);
34629
+ draftState.flushTimer = null;
34630
+ }
34631
+ };
34571
34632
  let toolUseCount = 0;
34572
34633
  const sigT0 = Date.now();
34573
34634
  const response = await askAgent(chatId, cleanText || text, {
@@ -34580,6 +34641,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34580
34641
  agentMode: effectiveAgentMode,
34581
34642
  ...effectiveThinking ? { thinkingLevel: effectiveThinking } : {},
34582
34643
  chatContext: { chatTitle: msg.chatTitle, threadId: msg.threadId },
34644
+ onStream: onStreamCb,
34583
34645
  onThinking: liveStatus || sessionLog ? (chunk) => {
34584
34646
  if (liveStatus) liveStatus.addThinking(chunk);
34585
34647
  if (sessionLog) sessionLog.logThinking(chunk);
@@ -34617,6 +34679,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34617
34679
  });
34618
34680
  }
34619
34681
  });
34682
+ stopDraftTimer2();
34620
34683
  const elapsedMs = Date.now() - sigT0;
34621
34684
  const elapsedSec = (elapsedMs / 1e3).toFixed(1);
34622
34685
  if (liveStatus && response.thinkingText?.trim()) {
@@ -34782,6 +34845,7 @@ Approve paid usage for this session?`,
34782
34845
  const userMsg = diagnoseAgentError(errMsg, chatId);
34783
34846
  await channel.sendText(chatId, userMsg, { parseMode: "plain" });
34784
34847
  } finally {
34848
+ stopDraftTimer();
34785
34849
  getTypingManager().release(chatId);
34786
34850
  const pending = pendingInterrupts.get(chatId);
34787
34851
  if (pending) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.29.0",
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",