cc-claw 0.18.2 → 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 +492 -88
  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.2" : (() => {
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 {
@@ -9717,6 +9717,14 @@ function parseAnalysisOutput(raw) {
9717
9717
  if (!VALID_CATEGORIES.includes(category)) continue;
9718
9718
  const confidence = confidenceMatch ? parseFloat(confidenceMatch[1].trim()) : 0.5;
9719
9719
  if (isNaN(confidence)) continue;
9720
+ let proposedDiff = diffMatch?.[1]?.trim() ?? "";
9721
+ if (proposedDiff.startsWith("```")) {
9722
+ proposedDiff = proposedDiff.replace(/^```[a-zA-Z0-9-]*\r?\n/, "");
9723
+ if (proposedDiff.endsWith("```")) {
9724
+ proposedDiff = proposedDiff.replace(/\r?\n```$/, "");
9725
+ }
9726
+ proposedDiff = proposedDiff.trim();
9727
+ }
9720
9728
  results.push({
9721
9729
  insight: insightMatch[1].trim(),
9722
9730
  category,
@@ -9724,7 +9732,7 @@ function parseAnalysisOutput(raw) {
9724
9732
  targetFile: targetMatch?.[1]?.trim() ?? "",
9725
9733
  confidence: Math.max(0, Math.min(1, confidence)),
9726
9734
  proposedAction: actionMatch?.[1]?.trim() ?? "",
9727
- proposedDiff: diffMatch?.[1]?.trim() ?? "",
9735
+ proposedDiff,
9728
9736
  conflictsWith: conflictsMatch?.[1]?.trim() ?? "none"
9729
9737
  });
9730
9738
  }
@@ -10128,6 +10136,181 @@ var init_analyze = __esm({
10128
10136
  }
10129
10137
  });
10130
10138
 
10139
+ // src/reflection/ai-apply.ts
10140
+ var ai_apply_exports = {};
10141
+ __export(ai_apply_exports, {
10142
+ applyWithAI: () => applyWithAI
10143
+ });
10144
+ import { spawn as spawn5 } from "child_process";
10145
+ import { createInterface as createInterface4 } from "readline";
10146
+ function buildApplyPrompt(originalContent, instruction, proposedDiff, targetFile) {
10147
+ return `You are a precise markdown document editor. Your ONLY job is to apply the requested change to the document while preserving its structural integrity.
10148
+
10149
+ === CURRENT FILE: ${targetFile} ===
10150
+ ${originalContent}
10151
+ === END FILE ===
10152
+
10153
+ === CHANGE TO APPLY ===
10154
+ Instruction: ${instruction}
10155
+
10156
+ Diff guidance:
10157
+ ${proposedDiff}
10158
+ === END CHANGE ===
10159
+
10160
+ Rules:
10161
+ 1. Place new content under the semantically correct section heading (## heading)
10162
+ 2. Maintain consistent formatting (bullet style, indentation) with the target section
10163
+ 3. Do NOT add, remove, or modify any content other than what the change requires
10164
+ 4. Do NOT wrap your output in markdown code fences
10165
+ 5. Do NOT add explanations, commentary, or notes
10166
+ 6. Preserve all existing headings and their order
10167
+ 7. Return ONLY the complete updated file content
10168
+
10169
+ Output the complete updated file now:`;
10170
+ }
10171
+ async function spawnApplyLLM(adapter, model2, prompt) {
10172
+ const config2 = adapter.buildSpawnConfig({
10173
+ prompt,
10174
+ model: model2,
10175
+ permMode: "yolo",
10176
+ allowedTools: []
10177
+ });
10178
+ const env = adapter.getEnv();
10179
+ let resultText = "";
10180
+ let accumulatedText = "";
10181
+ await new Promise((resolve) => {
10182
+ const proc = spawn5(config2.executable, config2.args, {
10183
+ env,
10184
+ stdio: ["ignore", "pipe", "pipe"],
10185
+ ...config2.cwd ? { cwd: config2.cwd } : {}
10186
+ });
10187
+ proc.stderr?.resume();
10188
+ const rl2 = createInterface4({ input: proc.stdout });
10189
+ const timeout = setTimeout(() => {
10190
+ warn("[ai-apply] LLM spawn timeout");
10191
+ rl2.close();
10192
+ proc.kill("SIGTERM");
10193
+ setTimeout(() => proc.kill("SIGKILL"), 2e3);
10194
+ }, AI_APPLY_TIMEOUT_MS);
10195
+ rl2.on("line", (line) => {
10196
+ if (!line.trim()) return;
10197
+ let msg;
10198
+ try {
10199
+ msg = JSON.parse(line);
10200
+ } catch {
10201
+ return;
10202
+ }
10203
+ const events = adapter.parseLine(msg);
10204
+ for (const ev of events) {
10205
+ if (ev.type === "text" && ev.text) accumulatedText = appendTextChunk(accumulatedText, ev.text);
10206
+ if (ev.type === "result") {
10207
+ resultText = ev.resultText || accumulatedText;
10208
+ if (adapter.shouldKillOnResult()) {
10209
+ rl2.close();
10210
+ proc.kill("SIGTERM");
10211
+ }
10212
+ }
10213
+ }
10214
+ });
10215
+ proc.on("error", () => {
10216
+ clearTimeout(timeout);
10217
+ resolve();
10218
+ });
10219
+ proc.on("close", () => {
10220
+ clearTimeout(timeout);
10221
+ resolve();
10222
+ });
10223
+ });
10224
+ if (!resultText) resultText = accumulatedText;
10225
+ return resultText;
10226
+ }
10227
+ function resolveApplyAdapter(chatId) {
10228
+ try {
10229
+ const adapter = getAdapterForChat(chatId);
10230
+ return { adapter, model: adapter.defaultModel };
10231
+ } catch {
10232
+ try {
10233
+ const adapter = getAdapter("claude");
10234
+ return { adapter, model: adapter.defaultModel };
10235
+ } catch {
10236
+ return null;
10237
+ }
10238
+ }
10239
+ }
10240
+ function extractHeadings(content) {
10241
+ return content.split("\n").filter((line) => /^#{1,6}\s/.test(line)).map((line) => line.trim());
10242
+ }
10243
+ function validateOutput(original, output2) {
10244
+ if (!output2.trim()) {
10245
+ return { valid: false, reason: "LLM returned empty output" };
10246
+ }
10247
+ const originalLines = original.split("\n").length;
10248
+ const outputLines = output2.split("\n").length;
10249
+ if (originalLines > 5 && outputLines < originalLines * MIN_LINE_RETENTION) {
10250
+ return {
10251
+ valid: false,
10252
+ reason: `Output has ${outputLines} lines vs original ${originalLines} (${Math.round(outputLines / originalLines * 100)}% retained, min ${MIN_LINE_RETENTION * 100}%)`
10253
+ };
10254
+ }
10255
+ const originalHeadings = extractHeadings(original);
10256
+ const outputHeadings = extractHeadings(output2);
10257
+ for (const heading4 of originalHeadings) {
10258
+ if (!outputHeadings.includes(heading4)) {
10259
+ return {
10260
+ valid: false,
10261
+ reason: `Missing heading: "${heading4}"`
10262
+ };
10263
+ }
10264
+ }
10265
+ if (output2.trim() === original.trim()) {
10266
+ return { valid: false, reason: "Output is identical to original (no changes applied)" };
10267
+ }
10268
+ return { valid: true };
10269
+ }
10270
+ function stripMarkdownFences(text) {
10271
+ const fencePattern = /^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/;
10272
+ const match = text.match(fencePattern);
10273
+ if (match) return match[1];
10274
+ return text;
10275
+ }
10276
+ async function applyWithAI(chatId, originalContent, instruction, proposedDiff, targetFile) {
10277
+ const resolved = resolveApplyAdapter(chatId);
10278
+ if (!resolved) {
10279
+ return { success: false, newContent: "", error: "No backend adapter available" };
10280
+ }
10281
+ const { adapter, model: model2 } = resolved;
10282
+ log(`[ai-apply] Using ${adapter.id}:${model2} for structural apply on ${targetFile}`);
10283
+ try {
10284
+ const prompt = buildApplyPrompt(originalContent, instruction, proposedDiff, targetFile);
10285
+ const rawOutput = await spawnApplyLLM(adapter, model2, prompt);
10286
+ if (!rawOutput.trim()) {
10287
+ return { success: false, newContent: "", error: "LLM returned empty response" };
10288
+ }
10289
+ const cleanOutput = stripMarkdownFences(rawOutput);
10290
+ const validation = validateOutput(originalContent, cleanOutput);
10291
+ if (!validation.valid) {
10292
+ return { success: false, newContent: "", error: `Validation failed: ${validation.reason}` };
10293
+ }
10294
+ log(`[ai-apply] Successfully applied change to ${targetFile}`);
10295
+ return { success: true, newContent: cleanOutput };
10296
+ } catch (err) {
10297
+ const msg = err instanceof Error ? err.message : String(err);
10298
+ warn(`[ai-apply] LLM apply failed: ${msg}`);
10299
+ return { success: false, newContent: "", error: msg };
10300
+ }
10301
+ }
10302
+ var AI_APPLY_TIMEOUT_MS, MIN_LINE_RETENTION;
10303
+ var init_ai_apply = __esm({
10304
+ "src/reflection/ai-apply.ts"() {
10305
+ "use strict";
10306
+ init_log();
10307
+ init_text_utils();
10308
+ init_backends();
10309
+ AI_APPLY_TIMEOUT_MS = 6e4;
10310
+ MIN_LINE_RETENTION = 0.7;
10311
+ }
10312
+ });
10313
+
10131
10314
  // src/reflection/apply.ts
10132
10315
  var apply_exports = {};
10133
10316
  __export(apply_exports, {
@@ -10258,7 +10441,31 @@ async function applyInsight(insightId) {
10258
10441
  writeFileSync5(backupPath, original, "utf-8");
10259
10442
  pruneBackups(absolutePath);
10260
10443
  }
10261
- const newContent = applyDiff(original, insight.proposedDiff, insight.proposedAction);
10444
+ let newContent;
10445
+ if (insight.proposedAction !== "create") {
10446
+ try {
10447
+ const { applyWithAI: applyWithAI2 } = await Promise.resolve().then(() => (init_ai_apply(), ai_apply_exports));
10448
+ const aiResult = await applyWithAI2(
10449
+ chatId,
10450
+ original,
10451
+ insight.insight,
10452
+ insight.proposedDiff,
10453
+ insight.targetFile
10454
+ );
10455
+ if (aiResult.success) {
10456
+ newContent = aiResult.newContent;
10457
+ log(`[reflection/apply] AI apply succeeded for insight #${insightId}`);
10458
+ } else {
10459
+ warn(`[reflection/apply] AI apply failed (${aiResult.error}), falling back to static diff`);
10460
+ newContent = applyDiff(original, insight.proposedDiff, insight.proposedAction);
10461
+ }
10462
+ } catch (aiErr) {
10463
+ warn(`[reflection/apply] AI apply threw (${aiErr}), falling back to static diff`);
10464
+ newContent = applyDiff(original, insight.proposedDiff, insight.proposedAction);
10465
+ }
10466
+ } else {
10467
+ newContent = applyDiff(original, insight.proposedDiff, insight.proposedAction);
10468
+ }
10262
10469
  writeFileSync5(absolutePath, newContent, "utf-8");
10263
10470
  const rollbackData = JSON.stringify({ original, backupPath, appliedAt: (/* @__PURE__ */ new Date()).toISOString() });
10264
10471
  updateInsightRollback(db3, insightId, rollbackData);
@@ -10947,6 +11154,7 @@ var init_detect = __esm({
10947
11154
  // src/agent.ts
10948
11155
  var agent_exports = {};
10949
11156
  __export(agent_exports, {
11157
+ CONTENT_SILENCE_TIMEOUT_ERROR: () => CONTENT_SILENCE_TIMEOUT_ERROR,
10950
11158
  FIRST_RESPONSE_TIMEOUT_ERROR: () => FIRST_RESPONSE_TIMEOUT_ERROR,
10951
11159
  FREE_SLOTS_EXHAUSTED: () => FREE_SLOTS_EXHAUSTED,
10952
11160
  askAgent: () => askAgent,
@@ -10956,8 +11164,8 @@ __export(agent_exports, {
10956
11164
  stopAgent: () => stopAgent,
10957
11165
  stopAllActiveAgents: () => stopAllActiveAgents
10958
11166
  });
10959
- import { spawn as spawn5 } from "child_process";
10960
- import { createInterface as createInterface4 } from "readline";
11167
+ import { spawn as spawn6 } from "child_process";
11168
+ import { createInterface as createInterface5 } from "readline";
10961
11169
  function isSyntheticChatId(chatId) {
10962
11170
  return chatId.startsWith("sq:") || chatId.startsWith("cron:");
10963
11171
  }
@@ -11021,7 +11229,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11021
11229
  const env = opts?.envOverride ? thinkingConfig?.envOverrides ? { ...opts.envOverride, ...thinkingConfig.envOverrides } : opts.envOverride : adapter.getEnv(thinkingConfig?.envOverrides);
11022
11230
  const finalArgs = thinkingConfig?.extraArgs ? [...config2.args, ...thinkingConfig.extraArgs] : config2.args;
11023
11231
  log(`[agent:spawn] backend=${adapter.id} exe=${config2.executable} model=${model2} timeout=${effectiveTimeout / 1e3}s cwd=${config2.cwd ?? "(inherited)"}`);
11024
- const proc = spawn5(config2.executable, finalArgs, {
11232
+ const proc = spawn6(config2.executable, finalArgs, {
11025
11233
  env,
11026
11234
  stdio: ["ignore", "pipe", "pipe"],
11027
11235
  detached: true,
@@ -11054,7 +11262,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11054
11262
  const pendingTools = /* @__PURE__ */ new Map();
11055
11263
  const stderrChunks = [];
11056
11264
  proc.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
11057
- const rl2 = createInterface4({ input: proc.stdout });
11265
+ const rl2 = createInterface5({ input: proc.stdout });
11058
11266
  let firstLine = true;
11059
11267
  const frTimeoutMs = adapter.id === "gemini" ? opts?.firstResponseTimeoutMs ?? FIRST_RESPONSE_TIMEOUT_MS : 0;
11060
11268
  let firstResponseTimer;
@@ -11070,6 +11278,20 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11070
11278
  }
11071
11279
  }, frTimeoutMs);
11072
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
+ }
11073
11295
  rl2.on("line", (line) => {
11074
11296
  if (!line.trim()) return;
11075
11297
  if (firstLine) {
@@ -11092,6 +11314,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11092
11314
  case "init":
11093
11315
  log(`[agent] Session init at ${elapsed()}`);
11094
11316
  if (ev.sessionId) sessionId = ev.sessionId;
11317
+ resetContentSilenceTimer();
11095
11318
  break;
11096
11319
  case "text":
11097
11320
  if (!gotModelContent) {
@@ -11101,6 +11324,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11101
11324
  firstResponseTimer = void 0;
11102
11325
  }
11103
11326
  }
11327
+ resetContentSilenceTimer();
11104
11328
  if (ev.text) {
11105
11329
  accumulatedText = appendTextChunk(accumulatedText, ev.text);
11106
11330
  if (opts?.onStream) opts.onStream(ev.text);
@@ -11114,6 +11338,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11114
11338
  firstResponseTimer = void 0;
11115
11339
  }
11116
11340
  }
11341
+ resetContentSilenceTimer();
11117
11342
  if (ev.text) {
11118
11343
  accumulatedThinking = appendTextChunk(accumulatedThinking, ev.text);
11119
11344
  if (opts?.onThinking) opts.onThinking(ev.text);
@@ -11127,6 +11352,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11127
11352
  firstResponseTimer = void 0;
11128
11353
  }
11129
11354
  }
11355
+ resetContentSilenceTimer();
11130
11356
  sawToolEvents = true;
11131
11357
  if (opts?.onToolAction && ev.toolName) {
11132
11358
  const toolInput = ev.toolInput ?? {};
@@ -11153,6 +11379,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11153
11379
  }
11154
11380
  break;
11155
11381
  case "tool_end":
11382
+ resetContentSilenceTimer();
11156
11383
  if (opts?.onToolAction) {
11157
11384
  const pending = ev.toolId ? pendingTools.get(ev.toolId) : void 0;
11158
11385
  if (pending) {
@@ -11206,6 +11433,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11206
11433
  proc.on("error", (err) => {
11207
11434
  clearTimeout(spawnTimeout);
11208
11435
  if (firstResponseTimer) clearTimeout(firstResponseTimer);
11436
+ if (contentSilenceTimer) clearTimeout(contentSilenceTimer);
11209
11437
  if (sigkillTimer) clearTimeout(sigkillTimer);
11210
11438
  rl2.close();
11211
11439
  cancelState.process = void 0;
@@ -11217,6 +11445,10 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11217
11445
  clearTimeout(firstResponseTimer);
11218
11446
  firstResponseTimer = void 0;
11219
11447
  }
11448
+ if (contentSilenceTimer) {
11449
+ clearTimeout(contentSilenceTimer);
11450
+ contentSilenceTimer = void 0;
11451
+ }
11220
11452
  if (sigkillTimer) {
11221
11453
  clearTimeout(sigkillTimer);
11222
11454
  sigkillTimer = void 0;
@@ -11237,6 +11469,11 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
11237
11469
  reject(new Error(`${FIRST_RESPONSE_TIMEOUT_ERROR}: No response from ${adapter.id} within ${frTimeoutMs / 1e3}s${stderr ? ` \u2014 ${stderr.slice(-300)}` : ""}`));
11238
11470
  return;
11239
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
+ }
11240
11477
  let msg = `Spawn timeout after ${effectiveTimeout / 1e3}s`;
11241
11478
  if (pendingTools.size > 0) {
11242
11479
  const tools2 = Array.from(pendingTools.values()).map((t) => typeof t === "string" ? t : t.name).join(", ");
@@ -11589,6 +11826,26 @@ async function askAgentImpl(chatId, userMessage, opts) {
11589
11826
  })();
11590
11827
  result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, retryOpts);
11591
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
+ }
11592
11849
  } else {
11593
11850
  if (!isSyntheticChatId(chatId)) {
11594
11851
  try {
@@ -11693,7 +11950,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
11693
11950
  if (!flag) return args;
11694
11951
  return [...args, ...flag, mcpConfigPath];
11695
11952
  }
11696
- 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;
11697
11954
  var init_agent = __esm({
11698
11955
  "src/agent.ts"() {
11699
11956
  "use strict";
@@ -11718,6 +11975,8 @@ var init_agent = __esm({
11718
11975
  chatLocks = /* @__PURE__ */ new Map();
11719
11976
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
11720
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";
11721
11980
  FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
11722
11981
  FREE_SLOTS_EXHAUSTED = "FREE_SLOTS_EXHAUSTED";
11723
11982
  GEMINI_FALLBACK_CHAIN = {
@@ -17024,8 +17283,8 @@ __export(analyze_exports2, {
17024
17283
  runIdentityAudit: () => runIdentityAudit,
17025
17284
  runSkillAudit: () => runSkillAudit
17026
17285
  });
17027
- import { spawn as spawn6 } from "child_process";
17028
- import { createInterface as createInterface5 } from "readline";
17286
+ import { spawn as spawn7 } from "child_process";
17287
+ import { createInterface as createInterface6 } from "readline";
17029
17288
  import { readFileSync as readFileSync12, existsSync as existsSync21, readdirSync as readdirSync11 } from "fs";
17030
17289
  import { join as join22 } from "path";
17031
17290
  import { homedir as homedir7 } from "os";
@@ -17047,6 +17306,14 @@ function parseOptimizeOutput(raw, validAreas) {
17047
17306
  if (!validAreas.includes(area)) continue;
17048
17307
  const severity = severityMatch?.[1]?.trim().toLowerCase() ?? "info";
17049
17308
  if (!VALID_SEVERITIES.includes(severity)) continue;
17309
+ let proposedDiff = diffMatch?.[1]?.trim() ?? "";
17310
+ if (proposedDiff.startsWith("```")) {
17311
+ proposedDiff = proposedDiff.replace(/^```[a-zA-Z0-9-]*\r?\n/, "");
17312
+ if (proposedDiff.endsWith("```")) {
17313
+ proposedDiff = proposedDiff.replace(/\r?\n```$/, "");
17314
+ }
17315
+ proposedDiff = proposedDiff.trim();
17316
+ }
17050
17317
  results.push({
17051
17318
  title: findingMatch[1].trim().slice(0, 80),
17052
17319
  area,
@@ -17054,7 +17321,7 @@ function parseOptimizeOutput(raw, validAreas) {
17054
17321
  detail: detailMatch?.[1]?.trim() ?? "",
17055
17322
  location: locationMatch?.[1]?.trim() ?? "",
17056
17323
  suggestion: suggestionMatch?.[1]?.trim() ?? "",
17057
- proposedDiff: diffMatch?.[1]?.trim() ?? ""
17324
+ proposedDiff
17058
17325
  });
17059
17326
  }
17060
17327
  return results;
@@ -17070,13 +17337,13 @@ async function spawnAnalysis2(adapter, model2, prompt, timeoutMs = ANALYSIS_TIME
17070
17337
  let resultText = "";
17071
17338
  let accumulatedText = "";
17072
17339
  await new Promise((resolve) => {
17073
- const proc = spawn6(config2.executable, config2.args, {
17340
+ const proc = spawn7(config2.executable, config2.args, {
17074
17341
  env,
17075
17342
  stdio: ["ignore", "pipe", "pipe"],
17076
17343
  ...config2.cwd ? { cwd: config2.cwd } : {}
17077
17344
  });
17078
17345
  proc.stderr?.resume();
17079
- const rl2 = createInterface5({ input: proc.stdout });
17346
+ const rl2 = createInterface6({ input: proc.stdout });
17080
17347
  const timeout = setTimeout(() => {
17081
17348
  warn(`[optimizer] Analysis timeout (${adapter.id}:${model2})`);
17082
17349
  rl2.close();
@@ -17558,8 +17825,8 @@ var init_ui2 = __esm({
17558
17825
  });
17559
17826
 
17560
17827
  // src/router/optimize.ts
17561
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, existsSync as existsSync22 } from "fs";
17562
- import { join as join23 } from "path";
17828
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, existsSync as existsSync22, readdirSync as readdirSync12, unlinkSync as unlinkSync6 } from "fs";
17829
+ import { join as join23, dirname as dirname4 } from "path";
17563
17830
  import { homedir as homedir8 } from "os";
17564
17831
  async function handleOptimizeCommand(chatId, channel, _args) {
17565
17832
  const { getModelDisplayInfo: getModelDisplayInfo2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
@@ -17664,13 +17931,38 @@ async function runIdentityAuditFlow(chatId, channel) {
17664
17931
  } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
17665
17932
  const modelInfo = getModelDisplayInfo2(chatId);
17666
17933
  if (!modelInfo) return;
17667
- await channel.sendText(
17934
+ const progressMsgId = typeof channel.sendTextReturningId === "function" ? await channel.sendTextReturningId(
17668
17935
  chatId,
17669
17936
  buildProgressMessage2("identity files", modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
17670
- { parseMode: "plain" }
17671
- );
17937
+ "plain"
17938
+ ) : void 0;
17939
+ if (!progressMsgId) {
17940
+ await channel.sendText(
17941
+ chatId,
17942
+ buildProgressMessage2("identity files", modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
17943
+ { parseMode: "plain" }
17944
+ );
17945
+ }
17946
+ const startTime = Date.now();
17947
+ const progressInterval = setInterval(async () => {
17948
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
17949
+ try {
17950
+ if (channel.sendTyping) await channel.sendTyping(chatId);
17951
+ if (progressMsgId && channel.editText) {
17952
+ await channel.editText(
17953
+ chatId,
17954
+ progressMsgId,
17955
+ buildProgressMessage2("identity files", modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel) + `
17956
+ \u23F3 Analyzing... (${elapsed}s)`,
17957
+ "plain"
17958
+ );
17959
+ }
17960
+ } catch {
17961
+ }
17962
+ }, 5e3);
17672
17963
  try {
17673
17964
  const result = await runIdentityAudit2(chatId);
17965
+ clearInterval(progressInterval);
17674
17966
  activeSessions.set(chatId, {
17675
17967
  chatId,
17676
17968
  result,
@@ -17685,6 +17977,7 @@ async function runIdentityAuditFlow(chatId, channel) {
17685
17977
  await channel.sendText(chatId, summary, { parseMode: "plain" });
17686
17978
  }
17687
17979
  } catch (e) {
17980
+ clearInterval(progressInterval);
17688
17981
  await channel.sendText(chatId, `Identity audit failed: ${e}`, { parseMode: "plain" });
17689
17982
  }
17690
17983
  }
@@ -17715,13 +18008,38 @@ async function runSkillAuditFlow(chatId, channel, skillName) {
17715
18008
  const modelInfo = getModelDisplayInfo2(chatId);
17716
18009
  if (!modelInfo) return;
17717
18010
  const skillPath = join23(homedir8(), ".cc-claw", "workspace", "skills", skillName, "SKILL.md");
17718
- await channel.sendText(
18011
+ const progressMsgId = typeof channel.sendTextReturningId === "function" ? await channel.sendTextReturningId(
17719
18012
  chatId,
17720
18013
  buildProgressMessage2(`skill: ${skillName}`, modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
17721
- { parseMode: "plain" }
17722
- );
18014
+ "plain"
18015
+ ) : void 0;
18016
+ if (!progressMsgId) {
18017
+ await channel.sendText(
18018
+ chatId,
18019
+ buildProgressMessage2(`skill: ${skillName}`, modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
18020
+ { parseMode: "plain" }
18021
+ );
18022
+ }
18023
+ const startTime = Date.now();
18024
+ const progressInterval = setInterval(async () => {
18025
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
18026
+ try {
18027
+ if (channel.sendTyping) await channel.sendTyping(chatId);
18028
+ if (progressMsgId && channel.editText) {
18029
+ await channel.editText(
18030
+ chatId,
18031
+ progressMsgId,
18032
+ buildProgressMessage2(`skill: ${skillName}`, modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel) + `
18033
+ \u23F3 Analyzing... (${elapsed}s)`,
18034
+ "plain"
18035
+ );
18036
+ }
18037
+ } catch {
18038
+ }
18039
+ }, 5e3);
17723
18040
  try {
17724
18041
  const result = await runSkillAudit2(chatId, skillPath);
18042
+ clearInterval(progressInterval);
17725
18043
  activeSessions.set(chatId, {
17726
18044
  chatId,
17727
18045
  result,
@@ -17736,6 +18054,7 @@ async function runSkillAuditFlow(chatId, channel, skillName) {
17736
18054
  await channel.sendText(chatId, summary, { parseMode: "plain" });
17737
18055
  }
17738
18056
  } catch (e) {
18057
+ clearInterval(progressInterval);
17739
18058
  await channel.sendText(chatId, `Skill audit failed: ${e}`, { parseMode: "plain" });
17740
18059
  }
17741
18060
  }
@@ -17787,8 +18106,28 @@ async function applyFinding(chatId, channel, index) {
17787
18106
  const backupPath = targetPath + `.bak.${Date.now()}`;
17788
18107
  writeFileSync7(backupPath, original, "utf-8");
17789
18108
  pruneBackups2(targetPath);
17790
- const { applyDiff: applyDiff2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
17791
- const newContent = applyDiff2(original, finding.proposedDiff, "replace");
18109
+ let newContent;
18110
+ try {
18111
+ const { applyWithAI: applyWithAI2 } = await Promise.resolve().then(() => (init_ai_apply(), ai_apply_exports));
18112
+ const aiResult = await applyWithAI2(
18113
+ chatId,
18114
+ original,
18115
+ finding.suggestion || finding.title,
18116
+ finding.proposedDiff,
18117
+ finding.location.split(":")[0] || "unknown"
18118
+ );
18119
+ if (aiResult.success) {
18120
+ newContent = aiResult.newContent;
18121
+ } else {
18122
+ warn(`[optimizer] AI apply failed (${aiResult.error}), falling back to static diff`);
18123
+ const { applyDiff: applyDiff2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
18124
+ newContent = applyDiff2(original, finding.proposedDiff, "replace");
18125
+ }
18126
+ } catch (aiErr) {
18127
+ warn(`[optimizer] AI apply threw (${aiErr}), falling back to static diff`);
18128
+ const { applyDiff: applyDiff2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
18129
+ newContent = applyDiff2(original, finding.proposedDiff, "replace");
18130
+ }
17792
18131
  if (newContent === original) {
17793
18132
  await channel.sendText(chatId, `\u26A0\uFE0F Diff produced no changes. The content may have already been modified.`, { parseMode: "plain" });
17794
18133
  session2.skipped.push(index);
@@ -17850,16 +18189,14 @@ function resolveTargetFile(location, auditTarget) {
17850
18189
  return null;
17851
18190
  }
17852
18191
  function pruneBackups2(absolutePath) {
17853
- const { readdirSync: readDir, unlinkSync: unlink4 } = __require("fs");
17854
- const { dirname: dirName } = __require("path");
17855
- const dir = dirName(absolutePath);
18192
+ const dir = dirname4(absolutePath);
17856
18193
  const baseName = absolutePath.split("/").pop() ?? "";
17857
18194
  try {
17858
- const backups = readDir(dir).filter((f) => f.startsWith(baseName + ".bak.")).sort().map((f) => join23(dir, f));
18195
+ const backups = readdirSync12(dir).filter((f) => f.startsWith(baseName + ".bak.")).sort().map((f) => join23(dir, f));
17859
18196
  while (backups.length > 3) {
17860
18197
  const oldest = backups.shift();
17861
18198
  try {
17862
- unlink4(oldest);
18199
+ unlinkSync6(oldest);
17863
18200
  } catch {
17864
18201
  }
17865
18202
  }
@@ -17870,6 +18207,7 @@ var activeSessions;
17870
18207
  var init_optimize = __esm({
17871
18208
  "src/router/optimize.ts"() {
17872
18209
  "use strict";
18210
+ init_log();
17873
18211
  activeSessions = /* @__PURE__ */ new Map();
17874
18212
  }
17875
18213
  });
@@ -19534,7 +19872,14 @@ var init_commands = __esm({
19534
19872
 
19535
19873
  // src/router/callbacks.ts
19536
19874
  import { readFile as readFile7 } from "fs/promises";
19537
- 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
+ };
19538
19883
  if (data.startsWith("menu:")) {
19539
19884
  const action = data.slice(5);
19540
19885
  const synth = { chatId, messageId: "", text: "", senderName: "User", type: "command", source: "telegram", command: "", commandArgs: "" };
@@ -19770,7 +20115,7 @@ ${PERM_MODES[chosen]}`,
19770
20115
  return;
19771
20116
  }
19772
20117
  removePendingPlan(chatId);
19773
- await channel.sendText(chatId, "\u2705 Approved. Executing...", { parseMode: "plain" });
20118
+ await replaceWithText("\u2705 Approved. Executing...");
19774
20119
  bypassBusyCheck.add(chatId);
19775
20120
  const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
19776
20121
  const overrideMsg = `[SYSTEM: Planning mode disabled. Execution APPROVED. Please execute the plan.]
@@ -20263,14 +20608,14 @@ ${rotationNote}`, { parseMode: "html" });
20263
20608
  }
20264
20609
  } else if (action === "discard") {
20265
20610
  pendingInterrupts.delete(targetChatId);
20266
- await channel.sendText(chatId, "\u{1F5D1} Message discarded.", { parseMode: "plain" });
20611
+ await replaceWithText("\u{1F5D1} Message discarded.");
20267
20612
  } else {
20268
20613
  await channel.sendText(chatId, "Message already processed or expired.", { parseMode: "plain" });
20269
20614
  }
20270
20615
  } else if (data.startsWith("sq:cancel:")) {
20271
20616
  const sqId = data.slice("sq:cancel:".length);
20272
20617
  stopAgent(sqId);
20273
- await channel.sendText(chatId, "\u{1F5FA} Side quest cancelled.", { parseMode: "plain" });
20618
+ await replaceWithText("\u{1F5FA} Side quest cancelled.");
20274
20619
  } else if (data.startsWith("fallback:")) {
20275
20620
  const parts = data.split(":");
20276
20621
  const targetBackend = parts[1];
@@ -20658,9 +21003,9 @@ __export(session_log_exports2, {
20658
21003
  startSessionLogCleanupTimer: () => startSessionLogCleanupTimer,
20659
21004
  tailSessionLog: () => tailSessionLog
20660
21005
  });
20661
- import { existsSync as existsSync23, mkdirSync as mkdirSync9, appendFileSync, readdirSync as readdirSync12, unlinkSync as unlinkSync6, statSync as statSync7, createReadStream } from "fs";
21006
+ import { existsSync as existsSync23, mkdirSync as mkdirSync9, appendFileSync, readdirSync as readdirSync13, unlinkSync as unlinkSync7, statSync as statSync7, createReadStream } from "fs";
20662
21007
  import { join as join24, basename as basename3 } from "path";
20663
- import { createInterface as createInterface6 } from "readline";
21008
+ import { createInterface as createInterface7 } from "readline";
20664
21009
  function getRetentionDays() {
20665
21010
  const env = process.env.SESSION_LOG_RETENTION_DAYS;
20666
21011
  if (env) {
@@ -20675,13 +21020,13 @@ function cleanupSessionLogs(retentionDays) {
20675
21020
  const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
20676
21021
  let cleaned = 0;
20677
21022
  try {
20678
- for (const file of readdirSync12(SESSION_LOGS_PATH)) {
21023
+ for (const file of readdirSync13(SESSION_LOGS_PATH)) {
20679
21024
  if (!file.startsWith("session-") || !file.endsWith(".log")) continue;
20680
21025
  const filePath = join24(SESSION_LOGS_PATH, file);
20681
21026
  try {
20682
21027
  const { mtimeMs } = statSync7(filePath);
20683
21028
  if (mtimeMs < cutoff) {
20684
- unlinkSync6(filePath);
21029
+ unlinkSync7(filePath);
20685
21030
  cleaned++;
20686
21031
  }
20687
21032
  } catch {
@@ -20705,7 +21050,7 @@ function startSessionLogCleanupTimer() {
20705
21050
  function listSessionLogs() {
20706
21051
  if (!existsSync23(SESSION_LOGS_PATH)) return [];
20707
21052
  const logs = [];
20708
- for (const file of readdirSync12(SESSION_LOGS_PATH)) {
21053
+ for (const file of readdirSync13(SESSION_LOGS_PATH)) {
20709
21054
  if (!file.startsWith("session-") || !file.endsWith(".log")) continue;
20710
21055
  const filePath = join24(SESSION_LOGS_PATH, file);
20711
21056
  try {
@@ -20731,7 +21076,7 @@ async function* tailSessionLog(filePath, lines = 50) {
20731
21076
  return;
20732
21077
  }
20733
21078
  const allLines = [];
20734
- const rl2 = createInterface6({
21079
+ const rl2 = createInterface7({
20735
21080
  input: createReadStream(filePath, { encoding: "utf-8" }),
20736
21081
  crlfDelay: Infinity
20737
21082
  });
@@ -20897,13 +21242,14 @@ function makeLiveStatus(chatId, channel, modelLabel, verboseLevel, showThinking)
20897
21242
  };
20898
21243
  return { liveStatus, toolCb };
20899
21244
  }
20900
- 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;
20901
21246
  var init_live_status = __esm({
20902
21247
  "src/router/live-status.ts"() {
20903
21248
  "use strict";
20904
21249
  init_log();
20905
21250
  init_helpers();
20906
- FLUSH_INTERVAL_MS = 1e3;
21251
+ FLUSH_INTERVAL_DM_MS = 1e3;
21252
+ FLUSH_INTERVAL_GROUP_MS = 3e3;
20907
21253
  MAX_THINKING_CHARS = 800;
20908
21254
  TRIM_THRESHOLD = 3500;
20909
21255
  MAX_ENTRIES = 200;
@@ -20925,6 +21271,11 @@ var init_live_status = __esm({
20925
21271
  nextFlushAllowedAt = 0;
20926
21272
  /** Tracks whether entries have been trimmed at least once (for display hint). */
20927
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
+ }
20928
21279
  /** Send the initial status message. Must be called before adding entries. */
20929
21280
  async init() {
20930
21281
  if (!this.channel.sendTextReturningId) return;
@@ -20933,7 +21284,7 @@ var init_live_status = __esm({
20933
21284
  this.messageId = await this.channel.sendTextReturningId(this.chatId, initial, "plain") ?? null;
20934
21285
  if (this.messageId) {
20935
21286
  this.flushTimer = setInterval(() => this.flush().catch(() => {
20936
- }), FLUSH_INTERVAL_MS);
21287
+ }), this.flushIntervalMs);
20937
21288
  }
20938
21289
  } catch (err) {
20939
21290
  log(`[live-status] init failed: ${err}`);
@@ -22158,7 +22509,7 @@ var init_wrap_backend = __esm({
22158
22509
  });
22159
22510
 
22160
22511
  // src/agents/runners/config-loader.ts
22161
- import { readFileSync as readFileSync14, readdirSync as readdirSync13, existsSync as existsSync24, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
22512
+ import { readFileSync as readFileSync14, readdirSync as readdirSync14, existsSync as existsSync24, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
22162
22513
  import { join as join26 } from "path";
22163
22514
  import { execFileSync as execFileSync2 } from "child_process";
22164
22515
  function resolveExecutable(config2) {
@@ -22312,7 +22663,7 @@ function loadAllRunnerConfigs() {
22312
22663
  mkdirSync10(RUNNERS_PATH, { recursive: true });
22313
22664
  return [];
22314
22665
  }
22315
- const files = readdirSync13(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
22666
+ const files = readdirSync14(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
22316
22667
  const configs = [];
22317
22668
  for (const file of files) {
22318
22669
  const config2 = loadRunnerConfig(join26(RUNNERS_PATH, file));
@@ -22343,7 +22694,7 @@ function watchRunnerConfigs(onChange) {
22343
22694
  watchedFiles.delete(prev);
22344
22695
  }
22345
22696
  }
22346
- const files = readdirSync13(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
22697
+ const files = readdirSync14(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
22347
22698
  for (const file of files) {
22348
22699
  const fullPath = join26(RUNNERS_PATH, file);
22349
22700
  if (watchedFiles.has(fullPath)) continue;
@@ -22646,7 +22997,26 @@ ${body.replace(/<[^>]*>/g, "").trim()}</code>
22646
22997
  });
22647
22998
 
22648
22999
  // src/channels/telegram.ts
22649
- 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
+ }
22650
23020
  function isFastPathMessage(msg) {
22651
23021
  if (msg.type === "command" && msg.command && FAST_PATH_COMMANDS.has(msg.command)) {
22652
23022
  return true;
@@ -22666,13 +23036,16 @@ function numericChatId(chatId) {
22666
23036
  const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
22667
23037
  return parseInt(raw);
22668
23038
  }
22669
- var FAST_PATH_COMMANDS, TelegramChannel;
23039
+ var MAX_RETRIES2, FALLBACK_RETRY_SEC, MAX_RETRY_WAIT_SEC, FAST_PATH_COMMANDS, TelegramChannel;
22670
23040
  var init_telegram2 = __esm({
22671
23041
  "src/channels/telegram.ts"() {
22672
23042
  "use strict";
22673
23043
  init_telegram();
22674
23044
  init_log();
22675
23045
  init_store5();
23046
+ MAX_RETRIES2 = 3;
23047
+ FALLBACK_RETRY_SEC = 3;
23048
+ MAX_RETRY_WAIT_SEC = 30;
22676
23049
  FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
22677
23050
  TelegramChannel = class {
22678
23051
  name = "telegram";
@@ -22823,11 +23196,12 @@ var init_telegram2 = __esm({
22823
23196
  return;
22824
23197
  }
22825
23198
  const data = ctx.callbackQuery.data;
23199
+ const messageId = ctx.callbackQuery.message?.message_id?.toString();
22826
23200
  ctx.answerCallbackQuery().catch(() => {
22827
23201
  });
22828
23202
  (async () => {
22829
23203
  for (const handler2 of this.callbackHandlers) {
22830
- await handler2(chatId, data, this);
23204
+ await handler2(chatId, data, this, messageId);
22831
23205
  }
22832
23206
  })().catch((err) => {
22833
23207
  error("[telegram] Callback handler error:", err);
@@ -22880,7 +23254,10 @@ var init_telegram2 = __esm({
22880
23254
  if (parseMode === "plain") {
22881
23255
  const plainChunks = splitMessage(text);
22882
23256
  for (const chunk of plainChunks) {
22883
- 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
+ );
22884
23261
  this.trackAgentMessage(sent.message_id, chatId);
22885
23262
  }
22886
23263
  return;
@@ -22889,32 +23266,44 @@ var init_telegram2 = __esm({
22889
23266
  const chunks = splitMessage(formatted);
22890
23267
  for (const chunk of chunks) {
22891
23268
  try {
22892
- const sent = await this.bot.api.sendMessage(numericChatId(chatId), chunk, {
22893
- parse_mode: "HTML",
22894
- ...threadOpts,
22895
- ...replyOpts
22896
- });
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
+ );
22897
23277
  this.trackAgentMessage(sent.message_id, chatId);
22898
23278
  } catch {
22899
- const sent = await this.bot.api.sendMessage(
22900
- numericChatId(chatId),
22901
- chunk.replace(/<[^>]+>/g, ""),
22902
- { ...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
+ )
22903
23286
  );
22904
23287
  this.trackAgentMessage(sent.message_id, chatId);
22905
23288
  }
22906
23289
  }
22907
23290
  }
22908
23291
  async sendVoice(chatId, audioBuffer, fileName) {
22909
- await this.bot.api.sendVoice(
22910
- numericChatId(chatId),
22911
- 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
+ )
22912
23298
  );
22913
23299
  }
22914
23300
  async sendFile(chatId, buffer, fileName) {
22915
- await this.bot.api.sendDocument(
22916
- numericChatId(chatId),
22917
- new InputFile(buffer, fileName)
23301
+ await withRetry(
23302
+ "sendFile",
23303
+ () => this.bot.api.sendDocument(
23304
+ numericChatId(chatId),
23305
+ new InputFile(buffer, fileName)
23306
+ )
22918
23307
  );
22919
23308
  }
22920
23309
  async downloadFile(fileId) {
@@ -22927,7 +23316,10 @@ var init_telegram2 = __esm({
22927
23316
  try {
22928
23317
  const formatted = parseMode === "html" ? text : parseMode === "plain" ? text : formatForTelegram(text);
22929
23318
  const opts = parseMode === "plain" ? {} : { parse_mode: "HTML" };
22930
- 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
+ );
22931
23323
  return msg.message_id.toString();
22932
23324
  } catch {
22933
23325
  return void 0;
@@ -22936,16 +23328,22 @@ var init_telegram2 = __esm({
22936
23328
  async editText(chatId, messageId, text, parseMode) {
22937
23329
  const formatted = parseMode === "html" ? text : formatForTelegram(text);
22938
23330
  try {
22939
- await this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
22940
- parse_mode: "HTML"
22941
- });
23331
+ await withRetry(
23332
+ "editText:html",
23333
+ () => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
23334
+ parse_mode: "HTML"
23335
+ })
23336
+ );
22942
23337
  return true;
22943
23338
  } catch {
22944
23339
  try {
22945
- await this.bot.api.editMessageText(
22946
- numericChatId(chatId),
22947
- parseInt(messageId),
22948
- 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
+ )
22949
23347
  );
22950
23348
  return true;
22951
23349
  } catch {
@@ -22975,16 +23373,22 @@ var init_telegram2 = __esm({
22975
23373
  const MAX_KEYBOARD_TEXT = 4e3;
22976
23374
  const safeText = text.length > MAX_KEYBOARD_TEXT ? text.slice(0, MAX_KEYBOARD_TEXT) + "\n\n\u2026(truncated)" : text;
22977
23375
  try {
22978
- const msg = await this.bot.api.sendMessage(numericChatId(chatId), safeText, {
22979
- reply_markup: keyboard
22980
- });
23376
+ const msg = await withRetry(
23377
+ "sendKeyboard",
23378
+ () => this.bot.api.sendMessage(numericChatId(chatId), safeText, {
23379
+ reply_markup: keyboard
23380
+ })
23381
+ );
22981
23382
  return msg.message_id.toString();
22982
23383
  } catch (err) {
22983
23384
  error(`[telegram] sendKeyboard failed (chat=${chatId}, textLen=${text.length}):`, err);
22984
23385
  try {
22985
- const fallbackMsg = await this.bot.api.sendMessage(numericChatId(chatId), "\u2B06\uFE0F (see above for details)", {
22986
- reply_markup: keyboard
22987
- });
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
+ );
22988
23392
  return fallbackMsg.message_id.toString();
22989
23393
  } catch (fallbackErr) {
22990
23394
  error(`[telegram] sendKeyboard fallback also failed:`, fallbackErr);
@@ -24333,7 +24737,7 @@ __export(service_exports, {
24333
24737
  serviceStatus: () => serviceStatus,
24334
24738
  uninstallService: () => uninstallService
24335
24739
  });
24336
- import { existsSync as existsSync29, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync7 } from "fs";
24740
+ import { existsSync as existsSync29, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
24337
24741
  import { execFileSync as execFileSync3, execSync as execSync6 } from "child_process";
24338
24742
  import { homedir as homedir10, platform } from "os";
24339
24743
  import { join as join30, dirname as dirname6 } from "path";
@@ -24442,7 +24846,7 @@ function uninstallMacOS() {
24442
24846
  execFileSync3("launchctl", ["unload", PLIST_PATH]);
24443
24847
  } catch {
24444
24848
  }
24445
- unlinkSync7(PLIST_PATH);
24849
+ unlinkSync8(PLIST_PATH);
24446
24850
  console.log(" Service uninstalled.");
24447
24851
  }
24448
24852
  function formatUptime(seconds) {
@@ -24531,7 +24935,7 @@ function uninstallLinux() {
24531
24935
  execFileSync3("systemctl", ["--user", "disable", "cc-claw"]);
24532
24936
  } catch {
24533
24937
  }
24534
- unlinkSync7(UNIT_PATH);
24938
+ unlinkSync8(UNIT_PATH);
24535
24939
  execFileSync3("systemctl", ["--user", "daemon-reload"]);
24536
24940
  console.log(" Service uninstalled.");
24537
24941
  }
@@ -25297,7 +25701,7 @@ __export(gemini_exports, {
25297
25701
  });
25298
25702
  import { existsSync as existsSync34, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync22, chmodSync } from "fs";
25299
25703
  import { join as join31 } from "path";
25300
- import { createInterface as createInterface7 } from "readline";
25704
+ import { createInterface as createInterface8 } from "readline";
25301
25705
  function requireDb() {
25302
25706
  if (!existsSync34(DB_PATH)) {
25303
25707
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
@@ -25370,7 +25774,7 @@ async function geminiList(globalOpts) {
25370
25774
  }
25371
25775
  async function geminiAddKey(globalOpts, opts) {
25372
25776
  await requireWriteDb();
25373
- const rl2 = createInterface7({ input: process.stdin, output: process.stdout });
25777
+ const rl2 = createInterface8({ input: process.stdin, output: process.stdout });
25374
25778
  const ask2 = (q) => new Promise((r) => rl2.question(q, r));
25375
25779
  const key = await ask2("Paste your Gemini API key: ");
25376
25780
  rl2.close();
@@ -25562,7 +25966,7 @@ __export(backend_cmd_factory_exports, {
25562
25966
  });
25563
25967
  import { existsSync as existsSync35, mkdirSync as mkdirSync15, readFileSync as readFileSync23 } from "fs";
25564
25968
  import { join as join32 } from "path";
25565
- import { createInterface as createInterface8 } from "readline";
25969
+ import { createInterface as createInterface9 } from "readline";
25566
25970
  function requireDb2() {
25567
25971
  if (!existsSync35(DB_PATH)) {
25568
25972
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
@@ -25623,7 +26027,7 @@ Add one with: cc-claw ${backend2} add-account or cc-claw ${backend2} add-key`)
25623
26027
  function makeAddKey(backend2, displayName) {
25624
26028
  return async function addKey(_globalOpts, opts) {
25625
26029
  await requireWriteDb2();
25626
- const rl2 = createInterface8({ input: process.stdin, output: process.stdout });
26030
+ const rl2 = createInterface9({ input: process.stdin, output: process.stdout });
25627
26031
  const ask2 = (q) => new Promise((r) => rl2.question(q, r));
25628
26032
  const key = await ask2(`Paste your ${displayName} API key: `);
25629
26033
  rl2.close();
@@ -27838,7 +28242,7 @@ var tui_exports = {};
27838
28242
  __export(tui_exports, {
27839
28243
  tuiCommand: () => tuiCommand
27840
28244
  });
27841
- import { createInterface as createInterface9 } from "readline";
28245
+ import { createInterface as createInterface10 } from "readline";
27842
28246
  import pc2 from "picocolors";
27843
28247
  async function tuiCommand(globalOpts, cmdOpts) {
27844
28248
  const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
@@ -27848,7 +28252,7 @@ async function tuiCommand(globalOpts, cmdOpts) {
27848
28252
  }
27849
28253
  const chatId = resolveChatId(globalOpts);
27850
28254
  const { chatSend: chatSend2 } = await Promise.resolve().then(() => (init_chat2(), chat_exports2));
27851
- const rl2 = createInterface9({
28255
+ const rl2 = createInterface10({
27852
28256
  input: process.stdin,
27853
28257
  output: process.stdout,
27854
28258
  prompt: pc2.cyan("you > "),
@@ -28649,7 +29053,7 @@ var init_optimize2 = __esm({
28649
29053
  var setup_exports = {};
28650
29054
  import { existsSync as existsSync55, writeFileSync as writeFileSync12, readFileSync as readFileSync26, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
28651
29055
  import { execFileSync as execFileSync5 } from "child_process";
28652
- import { createInterface as createInterface10 } from "readline";
29056
+ import { createInterface as createInterface11 } from "readline";
28653
29057
  import { join as join34 } from "path";
28654
29058
  function divider2() {
28655
29059
  console.log(dim("\u2500".repeat(55)));
@@ -29012,7 +29416,7 @@ var init_setup = __esm({
29012
29416
  "src/setup.ts"() {
29013
29417
  "use strict";
29014
29418
  init_paths();
29015
- rl = createInterface10({ input: process.stdin, output: process.stdout });
29419
+ rl = createInterface11({ input: process.stdin, output: process.stdout });
29016
29420
  ask = (q) => new Promise((resolve) => rl.question(q, resolve));
29017
29421
  bold = (s) => `\x1B[1m${s}\x1B[0m`;
29018
29422
  green = (s) => `\x1B[32m${s}\x1B[0m`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.18.2",
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",