cc-claw 0.29.1 → 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.
- package/dist/cli.js +304 -87
- 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.
|
|
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.
|
|
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
|
-
|
|
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,19 +7640,20 @@ 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;
|
|
7594
7654
|
MAX_QUEUE_SIZE = 60;
|
|
7595
7655
|
EDIT_PRESSURE_THRESHOLD = MAX_QUEUE_SIZE / 2;
|
|
7596
|
-
MAX_PER_CHAT_QUEUE =
|
|
7656
|
+
MAX_PER_CHAT_QUEUE = 30;
|
|
7597
7657
|
MAX_TOTAL_PAUSE_MS = 5 * 60 * 1e3;
|
|
7598
7658
|
CIRCUIT_TRIP_THRESHOLD = 3;
|
|
7599
7659
|
CIRCUIT_TRIP_WINDOW_MS = 5 * 60 * 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
|
|
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) {
|
|
@@ -14088,18 +14162,24 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
14088
14162
|
let contentSilenceTimer;
|
|
14089
14163
|
const silenceTimeoutMs = CONTENT_SILENCE_TIMEOUT_MS;
|
|
14090
14164
|
let silenceResetCount = 0;
|
|
14091
|
-
const MAX_SILENCE_RESETS =
|
|
14165
|
+
const MAX_SILENCE_RESETS = 3;
|
|
14092
14166
|
function resetContentSilenceTimer() {
|
|
14093
14167
|
if (silenceTimeoutMs <= 0) return;
|
|
14094
14168
|
if (contentSilenceTimer) clearTimeout(contentSilenceTimer);
|
|
14095
14169
|
contentSilenceTimer = setTimeout(() => {
|
|
14096
14170
|
if (cancelState.cancelled || timedOut) return;
|
|
14097
14171
|
if (pendingTools.size > 0 && silenceResetCount < MAX_SILENCE_RESETS) {
|
|
14098
|
-
|
|
14099
|
-
const
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14172
|
+
const now = Date.now();
|
|
14173
|
+
const hungTool = Array.from(pendingTools.values()).find((t) => now - t.startedAt > silenceTimeoutMs);
|
|
14174
|
+
if (hungTool) {
|
|
14175
|
+
warn(`[agent] Tool "${hungTool.name}" has been running for ${Math.round((now - hungTool.startedAt) / 1e3)}s \u2014 treating as hung, killing`);
|
|
14176
|
+
} else {
|
|
14177
|
+
silenceResetCount++;
|
|
14178
|
+
const tools2 = Array.from(pendingTools.values()).map((t) => `${t.name} (${Math.round((now - t.startedAt) / 1e3)}s)`).join(", ");
|
|
14179
|
+
log(`[agent] Content silence timer fired but ${pendingTools.size} tool(s) still running (${tools2}) \u2014 resetting (${silenceResetCount}/${MAX_SILENCE_RESETS})`);
|
|
14180
|
+
resetContentSilenceTimer();
|
|
14181
|
+
return;
|
|
14182
|
+
}
|
|
14103
14183
|
}
|
|
14104
14184
|
warn(`[agent] Content silence timeout after ${silenceTimeoutMs / 1e3}s for ${adapter.id} \u2014 no content events, killing`);
|
|
14105
14185
|
timedOut = true;
|
|
@@ -14178,7 +14258,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
14178
14258
|
sawToolEvents = true;
|
|
14179
14259
|
if (opts?.onToolAction && ev.toolName) {
|
|
14180
14260
|
const toolInput = ev.toolInput ?? {};
|
|
14181
|
-
if (ev.toolId) pendingTools.set(ev.toolId, { name: ev.toolName, input: toolInput });
|
|
14261
|
+
if (ev.toolId) pendingTools.set(ev.toolId, { name: ev.toolName, input: toolInput, startedAt: Date.now() });
|
|
14182
14262
|
opts.onToolAction(ev.toolName, toolInput, void 0, ev.toolId).catch((err) => {
|
|
14183
14263
|
error("[agent] tool action error:", err);
|
|
14184
14264
|
});
|
|
@@ -14222,7 +14302,6 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
14222
14302
|
input += ev.usage.input;
|
|
14223
14303
|
output2 += ev.usage.output;
|
|
14224
14304
|
cacheRead += ev.usage.cacheRead;
|
|
14225
|
-
contextSize = ev.usage.input + (ev.usage.cacheRead ?? 0);
|
|
14226
14305
|
}
|
|
14227
14306
|
break;
|
|
14228
14307
|
case "result":
|
|
@@ -14240,7 +14319,6 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
14240
14319
|
input = ev.usage.input;
|
|
14241
14320
|
output2 = ev.usage.output;
|
|
14242
14321
|
cacheRead = ev.usage.cacheRead;
|
|
14243
|
-
contextSize = ev.usage.input + (ev.usage.cacheRead ?? 0);
|
|
14244
14322
|
}
|
|
14245
14323
|
if (adapter.shouldKillOnResult()) {
|
|
14246
14324
|
try {
|
|
@@ -14323,7 +14401,7 @@ Partial output: ${accumulatedText.slice(-500)}`;
|
|
|
14323
14401
|
return;
|
|
14324
14402
|
}
|
|
14325
14403
|
const cleanedResult = stripThinkingContent(resultText || accumulatedText);
|
|
14326
|
-
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 });
|
|
14327
14405
|
});
|
|
14328
14406
|
});
|
|
14329
14407
|
}
|
|
@@ -14542,9 +14620,12 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
14542
14620
|
activeChats.set(chatId, cancelState2);
|
|
14543
14621
|
try {
|
|
14544
14622
|
let messageHistory;
|
|
14623
|
+
let apiContextSize;
|
|
14545
14624
|
if (adapter.type === "api") {
|
|
14546
14625
|
const contextWindow = adapter.contextWindow[resolvedModel2] ?? 8192;
|
|
14547
|
-
|
|
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;
|
|
14548
14629
|
}
|
|
14549
14630
|
const sdResult = await adapter.streamDirect(fullPrompt, resolvedModel2, {
|
|
14550
14631
|
timeoutMs: timeoutMs ?? 3e5,
|
|
@@ -14560,23 +14641,29 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
14560
14641
|
});
|
|
14561
14642
|
if (!isSyntheticChatId(chatId)) {
|
|
14562
14643
|
appendToLog(chatId, userMessage, sdResult.text, adapter.id, resolvedModel2, null);
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
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
|
+
}
|
|
14575
14662
|
}
|
|
14576
14663
|
}
|
|
14577
14664
|
const sdUsage = sdResult.usage ?? { input: 0, output: 0 };
|
|
14578
14665
|
if (sdUsage.input + sdUsage.output > 0) {
|
|
14579
|
-
addUsage(chatId, sdUsage.input, sdUsage.output, 0, resolvedModel2);
|
|
14666
|
+
addUsage(chatId, sdUsage.input, sdUsage.output, 0, resolvedModel2, adapter.id, apiContextSize);
|
|
14580
14667
|
}
|
|
14581
14668
|
if (cancelState2.cancelled) {
|
|
14582
14669
|
return { text: "Stopped.", usage: { input: sdUsage.input, output: sdUsage.output, cacheRead: 0 } };
|
|
@@ -14878,18 +14965,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
14878
14965
|
}
|
|
14879
14966
|
if (result.resultText && !isSyntheticChatId(chatId)) {
|
|
14880
14967
|
appendToLog(chatId, userMessage, result.resultText, adapter.id, model2 ?? null, result.sessionId ?? null);
|
|
14881
|
-
const AUTO_SUMMARIZE_THRESHOLD = 30;
|
|
14882
14968
|
const pairCount = profile !== "chat" ? getMessagePairCount(chatId) : 0;
|
|
14883
|
-
if (pairCount >=
|
|
14969
|
+
if (pairCount >= 30) {
|
|
14884
14970
|
log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
|
|
14885
|
-
|
|
14886
|
-
if (saved) {
|
|
14887
|
-
clearSession(chatId);
|
|
14888
|
-
opts?.onCompaction?.(chatId);
|
|
14889
|
-
}
|
|
14890
|
-
}).catch((err) => {
|
|
14891
|
-
warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
|
|
14892
|
-
});
|
|
14971
|
+
runCompaction(chatId, "30-pair-threshold", opts?.onCompaction);
|
|
14893
14972
|
}
|
|
14894
14973
|
}
|
|
14895
14974
|
return {
|
|
@@ -14917,7 +14996,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
|
|
|
14917
14996
|
if (!flag) return args;
|
|
14918
14997
|
return [...args, ...flag, mcpConfigPath, "--strict-mcp-config"];
|
|
14919
14998
|
}
|
|
14920
|
-
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;
|
|
14921
15000
|
var init_agent = __esm({
|
|
14922
15001
|
"src/agent.ts"() {
|
|
14923
15002
|
"use strict";
|
|
@@ -14934,7 +15013,6 @@ var init_agent = __esm({
|
|
|
14934
15013
|
init_strip_thinking();
|
|
14935
15014
|
init_text_utils();
|
|
14936
15015
|
init_session_log();
|
|
14937
|
-
init_api_context();
|
|
14938
15016
|
init_summarize();
|
|
14939
15017
|
init_quota();
|
|
14940
15018
|
init_store5();
|
|
@@ -14945,6 +15023,7 @@ var init_agent = __esm({
|
|
|
14945
15023
|
init_unified_config();
|
|
14946
15024
|
init_mcp_config();
|
|
14947
15025
|
activeChats = /* @__PURE__ */ new Map();
|
|
15026
|
+
compactionInFlight = /* @__PURE__ */ new Set();
|
|
14948
15027
|
chatLocks = /* @__PURE__ */ new Map();
|
|
14949
15028
|
SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
14950
15029
|
FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
|
|
@@ -15112,7 +15191,9 @@ async function runWeeklySweep(chatId, channel, backendId, model2) {
|
|
|
15112
15191
|
buttons.push([{ label: "Review Now", data: "mem:opt:start", style: "success" }]);
|
|
15113
15192
|
}
|
|
15114
15193
|
buttons.push([{ label: "Dismiss", data: "mem:sweep:dismiss" }]);
|
|
15115
|
-
|
|
15194
|
+
if (channel) {
|
|
15195
|
+
await sendOrEditKeyboard(chatId, channel, void 0, lines.join("\n"), buttons);
|
|
15196
|
+
}
|
|
15116
15197
|
return { suggestionsCount, cleanedUp };
|
|
15117
15198
|
} catch (err) {
|
|
15118
15199
|
const msg = errorMessage(err);
|
|
@@ -22597,6 +22678,7 @@ var init_ollama2 = __esm({
|
|
|
22597
22678
|
async streamDirect(prompt, model2, opts) {
|
|
22598
22679
|
const cleanPrompt = stripForLocalModel(prompt);
|
|
22599
22680
|
let disableThinking = false;
|
|
22681
|
+
let modelContextWindow;
|
|
22600
22682
|
try {
|
|
22601
22683
|
const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
|
|
22602
22684
|
const modelRecord = OllamaStore.getModelByName(model2);
|
|
@@ -22605,8 +22687,14 @@ var init_ollama2 = __esm({
|
|
|
22605
22687
|
} else if (opts?.thinkingLevel === "off") {
|
|
22606
22688
|
disableThinking = true;
|
|
22607
22689
|
}
|
|
22690
|
+
if (modelRecord?.contextWindow && modelRecord.contextWindow > 4096) {
|
|
22691
|
+
modelContextWindow = modelRecord.contextWindow;
|
|
22692
|
+
}
|
|
22608
22693
|
} catch {
|
|
22609
22694
|
}
|
|
22695
|
+
const ollamaProviderOpts = {};
|
|
22696
|
+
if (disableThinking) ollamaProviderOpts.think = false;
|
|
22697
|
+
if (modelContextWindow) ollamaProviderOpts.num_ctx = modelContextWindow;
|
|
22610
22698
|
const apiOpts = {
|
|
22611
22699
|
timeoutMs: opts?.timeoutMs,
|
|
22612
22700
|
onStream: opts?.onStream,
|
|
@@ -22615,7 +22703,7 @@ var init_ollama2 = __esm({
|
|
|
22615
22703
|
permMode: opts?.permMode,
|
|
22616
22704
|
thinkingLevel: opts?.thinkingLevel,
|
|
22617
22705
|
onThinking: opts?.onThinking,
|
|
22618
|
-
...
|
|
22706
|
+
...Object.keys(ollamaProviderOpts).length > 0 ? { providerOptions: { ollama: ollamaProviderOpts } } : {}
|
|
22619
22707
|
};
|
|
22620
22708
|
const result = await this.streamDirectWithHistory(
|
|
22621
22709
|
cleanPrompt,
|
|
@@ -22701,7 +22789,28 @@ var init_openrouter = __esm({
|
|
|
22701
22789
|
}
|
|
22702
22790
|
summarizerModel = DEFAULT_FREE_MODEL;
|
|
22703
22791
|
pricing = {};
|
|
22704
|
-
|
|
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
|
+
}
|
|
22705
22814
|
// ── Vercel AI SDK provider ────────────────────────────────────────
|
|
22706
22815
|
/**
|
|
22707
22816
|
* Create the Vercel AI SDK LanguageModel for a given model ID.
|
|
@@ -24547,7 +24656,7 @@ function getEditCoordinator() {
|
|
|
24547
24656
|
function resetEditCoordinator() {
|
|
24548
24657
|
EditCoordinator.resetInstance();
|
|
24549
24658
|
}
|
|
24550
|
-
var TICK_INTERVAL_MS, MAX_EDITS_PER_WINDOW, EDIT_WINDOW_MS, EditCoordinator;
|
|
24659
|
+
var TICK_INTERVAL_MS, MAX_EDITS_PER_WINDOW, EDIT_WINDOW_MS, MIN_FLUSH_GAP_DM_MS, MIN_FLUSH_GAP_GROUP_MS, EditCoordinator;
|
|
24551
24660
|
var init_edit_coordinator = __esm({
|
|
24552
24661
|
"src/channels/edit-coordinator.ts"() {
|
|
24553
24662
|
"use strict";
|
|
@@ -24555,10 +24664,14 @@ var init_edit_coordinator = __esm({
|
|
|
24555
24664
|
TICK_INTERVAL_MS = 1e3;
|
|
24556
24665
|
MAX_EDITS_PER_WINDOW = 4;
|
|
24557
24666
|
EDIT_WINDOW_MS = 6e4;
|
|
24667
|
+
MIN_FLUSH_GAP_DM_MS = 2e3;
|
|
24668
|
+
MIN_FLUSH_GAP_GROUP_MS = 4e3;
|
|
24558
24669
|
EditCoordinator = class _EditCoordinator {
|
|
24559
24670
|
static instance = null;
|
|
24560
24671
|
/** Active streams indexed by messageId. */
|
|
24561
24672
|
activeStreams = /* @__PURE__ */ new Map();
|
|
24673
|
+
/** Last flush timestamp per stream — prevents flushing faster than the throttle can drain. */
|
|
24674
|
+
lastFlushAt = /* @__PURE__ */ new Map();
|
|
24562
24675
|
/** Per-message edit tracking for the sliding window cap. */
|
|
24563
24676
|
perMessageEditCount = /* @__PURE__ */ new Map();
|
|
24564
24677
|
/** Single flush timer shared across all streams. */
|
|
@@ -24599,6 +24712,7 @@ var init_edit_coordinator = __esm({
|
|
|
24599
24712
|
unregister(messageId) {
|
|
24600
24713
|
this.activeStreams.delete(messageId);
|
|
24601
24714
|
this.perMessageEditCount.delete(messageId);
|
|
24715
|
+
this.lastFlushAt.delete(messageId);
|
|
24602
24716
|
this.rebuildKeys();
|
|
24603
24717
|
log(`[edit-coordinator] unregistered stream ${messageId} (${this.activeStreams.size} remaining)`);
|
|
24604
24718
|
if (this.activeStreams.size === 0 && this.flushTimer) {
|
|
@@ -24616,6 +24730,7 @@ var init_edit_coordinator = __esm({
|
|
|
24616
24730
|
}
|
|
24617
24731
|
this.activeStreams.clear();
|
|
24618
24732
|
this.perMessageEditCount.clear();
|
|
24733
|
+
this.lastFlushAt.clear();
|
|
24619
24734
|
this.streamKeys = [];
|
|
24620
24735
|
this.roundRobinIndex = 0;
|
|
24621
24736
|
}
|
|
@@ -24644,6 +24759,17 @@ var init_edit_coordinator = __esm({
|
|
|
24644
24759
|
}
|
|
24645
24760
|
}
|
|
24646
24761
|
// ── Internal ──────────────────────────────────────────────────────────
|
|
24762
|
+
/** Check whether enough time has passed since the last flush for this stream.
|
|
24763
|
+
* Group chats need longer gaps (4s) to match the throttle's group pacing (3.5s).
|
|
24764
|
+
* Without this, the coordinator pushes edits faster than the throttle drains them,
|
|
24765
|
+
* causing per-chat queue buildup. */
|
|
24766
|
+
canFlushStream(messageId, stream) {
|
|
24767
|
+
const last = this.lastFlushAt.get(messageId);
|
|
24768
|
+
if (last === void 0) return true;
|
|
24769
|
+
const chatId = stream.getChatId();
|
|
24770
|
+
const minGap = parseInt(chatId) < 0 ? MIN_FLUSH_GAP_GROUP_MS : MIN_FLUSH_GAP_DM_MS;
|
|
24771
|
+
return Date.now() - last >= minGap;
|
|
24772
|
+
}
|
|
24647
24773
|
/** Rebuild the ordered keys array after registration changes. */
|
|
24648
24774
|
rebuildKeys() {
|
|
24649
24775
|
this.streamKeys = Array.from(this.activeStreams.keys());
|
|
@@ -24663,11 +24789,12 @@ var init_edit_coordinator = __esm({
|
|
|
24663
24789
|
const idx = (startIdx + tried) % this.streamKeys.length;
|
|
24664
24790
|
const messageId = this.streamKeys[idx];
|
|
24665
24791
|
const stream = this.activeStreams.get(messageId);
|
|
24666
|
-
if (stream && this.canEditMessage(messageId)) {
|
|
24792
|
+
if (stream && this.canEditMessage(messageId) && this.canFlushStream(messageId, stream)) {
|
|
24667
24793
|
this.roundRobinIndex = (idx + 1) % this.streamKeys.length;
|
|
24668
24794
|
try {
|
|
24669
24795
|
await stream.flush();
|
|
24670
24796
|
this.recordEdit(messageId);
|
|
24797
|
+
this.lastFlushAt.set(messageId, Date.now());
|
|
24671
24798
|
} catch {
|
|
24672
24799
|
}
|
|
24673
24800
|
return;
|
|
@@ -24782,6 +24909,7 @@ var init_live_status = __esm({
|
|
|
24782
24909
|
/** Spinner frame counter — advances on each flush for animation. */
|
|
24783
24910
|
spinnerFrame = 0;
|
|
24784
24911
|
/** Timestamp of last successful edit — used for heartbeat force-through. */
|
|
24912
|
+
lastSuccessfulFlushAt = 0;
|
|
24785
24913
|
/** Callback to restart typing indicator as fallback. */
|
|
24786
24914
|
onTypingFallback;
|
|
24787
24915
|
/** Set a callback that restarts the typing indicator loop as a fallback. */
|
|
@@ -25360,10 +25488,12 @@ function getTypingManager() {
|
|
|
25360
25488
|
function resetTypingManager() {
|
|
25361
25489
|
TypingManager.resetInstance();
|
|
25362
25490
|
}
|
|
25363
|
-
var TypingManager;
|
|
25491
|
+
var MAX_TYPING_DURATION_MS, TypingManager;
|
|
25364
25492
|
var init_typing_manager = __esm({
|
|
25365
25493
|
"src/channels/typing-manager.ts"() {
|
|
25366
25494
|
"use strict";
|
|
25495
|
+
init_log();
|
|
25496
|
+
MAX_TYPING_DURATION_MS = 15 * 60 * 1e3;
|
|
25367
25497
|
TypingManager = class _TypingManager {
|
|
25368
25498
|
static instance = null;
|
|
25369
25499
|
activeChats = /* @__PURE__ */ new Map();
|
|
@@ -25385,11 +25515,17 @@ var init_typing_manager = __esm({
|
|
|
25385
25515
|
}
|
|
25386
25516
|
channel.sendTyping?.(chatId).catch(() => {
|
|
25387
25517
|
});
|
|
25518
|
+
const acquiredAt = Date.now();
|
|
25388
25519
|
const timer = setInterval(() => {
|
|
25520
|
+
if (Date.now() - acquiredAt > MAX_TYPING_DURATION_MS) {
|
|
25521
|
+
warn(`[typing-manager] Auto-releasing chat ${chatId} after ${MAX_TYPING_DURATION_MS / 6e4}min (likely leak)`);
|
|
25522
|
+
this.forceRelease(chatId);
|
|
25523
|
+
return;
|
|
25524
|
+
}
|
|
25389
25525
|
channel.sendTyping?.(chatId).catch(() => {
|
|
25390
25526
|
});
|
|
25391
25527
|
}, 4e3);
|
|
25392
|
-
this.activeChats.set(chatId, { refCount: 1, timer });
|
|
25528
|
+
this.activeChats.set(chatId, { refCount: 1, timer, acquiredAt });
|
|
25393
25529
|
}
|
|
25394
25530
|
/**
|
|
25395
25531
|
* Stop showing typing for this agent's perspective.
|
|
@@ -25404,6 +25540,13 @@ var init_typing_manager = __esm({
|
|
|
25404
25540
|
this.activeChats.delete(chatId);
|
|
25405
25541
|
}
|
|
25406
25542
|
}
|
|
25543
|
+
/** Force-release a chat regardless of refCount (for leak recovery). */
|
|
25544
|
+
forceRelease(chatId) {
|
|
25545
|
+
const entry = this.activeChats.get(chatId);
|
|
25546
|
+
if (!entry) return;
|
|
25547
|
+
clearInterval(entry.timer);
|
|
25548
|
+
this.activeChats.delete(chatId);
|
|
25549
|
+
}
|
|
25407
25550
|
/** Clean shutdown — clear all timers. */
|
|
25408
25551
|
shutdown() {
|
|
25409
25552
|
for (const [, entry] of this.activeChats) {
|
|
@@ -29319,6 +29462,7 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
|
|
|
29319
29462
|
const summarized = await summarizeSession(chatId);
|
|
29320
29463
|
clearSession(chatId);
|
|
29321
29464
|
clearChatPaidSlots(chatId);
|
|
29465
|
+
clearUsage(chatId);
|
|
29322
29466
|
setSessionStartedAt(chatId);
|
|
29323
29467
|
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "New session started", detail: { field: "session", action: "reset", summarized } });
|
|
29324
29468
|
if (typeof channel.sendKeyboard === "function" && oldSessionId) {
|
|
@@ -29372,6 +29516,7 @@ async function handleClearCommand(chatId, _commandArgs, _msg, channel) {
|
|
|
29372
29516
|
stopAllSideQuests(chatId);
|
|
29373
29517
|
clearSession(chatId);
|
|
29374
29518
|
clearChatPaidSlots(chatId);
|
|
29519
|
+
clearUsage(chatId);
|
|
29375
29520
|
setSessionStartedAt(chatId);
|
|
29376
29521
|
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "Session cleared (no summary)", detail: { field: "session", action: "clear" } });
|
|
29377
29522
|
await channel.sendText(chatId, "\u{1F9FD} Session cleared. No summary saved.", { parseMode: "plain", priority: 0 /* P0_CRITICAL */ });
|
|
@@ -29448,6 +29593,17 @@ async function handleSummarizeCommand(chatId, commandArgs, msg, channel) {
|
|
|
29448
29593
|
}
|
|
29449
29594
|
}
|
|
29450
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
|
+
}
|
|
29451
29607
|
async function handleStatusCommand(chatId, commandArgs, msg, channel) {
|
|
29452
29608
|
const sessionId = getSessionId(chatId);
|
|
29453
29609
|
const cwd = getCwd(chatId);
|
|
@@ -29464,12 +29620,21 @@ async function handleStatusCommand(chatId, commandArgs, msg, channel) {
|
|
|
29464
29620
|
const thinking2 = getThinkingLevel(chatId);
|
|
29465
29621
|
const mode = getMode(chatId);
|
|
29466
29622
|
const modelSig = getModelSignature(chatId);
|
|
29467
|
-
|
|
29468
|
-
|
|
29469
|
-
|
|
29470
|
-
|
|
29471
|
-
|
|
29472
|
-
|
|
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
|
+
}
|
|
29473
29638
|
const bootRow = getDb().prepare("SELECT value FROM meta WHERE key = 'boot_time'").get();
|
|
29474
29639
|
let uptimeStr = "unknown";
|
|
29475
29640
|
if (bootRow) {
|
|
@@ -29532,11 +29697,12 @@ async function handleStatusCommand(chatId, commandArgs, msg, channel) {
|
|
|
29532
29697
|
`\u{1F4AD} Think: ${thinking2} \xB7 Mode: ${mode}`,
|
|
29533
29698
|
`\u{1F916} Agents: ${getAgentMode(chatId)}`,
|
|
29534
29699
|
`\u{1F507} Voice: ${voice2 ? "on" : "off"} \xB7 Sig: ${modelSig}`,
|
|
29700
|
+
`\u{1F4DD} Summarizer: ${summarizerStatusLine(chatId, adapter)}`,
|
|
29535
29701
|
``,
|
|
29536
29702
|
buildSectionHeader("Session"),
|
|
29537
29703
|
`\u{1F4CB} ${sessionId ?? "no active session"}`,
|
|
29538
29704
|
`\u{1F4C1} ${cwd ?? "default workspace"}`,
|
|
29539
|
-
|
|
29705
|
+
contextLine,
|
|
29540
29706
|
...sqCount > 0 ? [`\u{1F5FA} Side quests: ${sqCount} active`] : [],
|
|
29541
29707
|
``,
|
|
29542
29708
|
buildSectionHeader("Usage"),
|
|
@@ -29947,8 +30113,17 @@ async function handleSummarizerCommand(chatId, commandArgs, msg, channel) {
|
|
|
29947
30113
|
} catch {
|
|
29948
30114
|
adapter = null;
|
|
29949
30115
|
}
|
|
29950
|
-
const current =
|
|
29951
|
-
const
|
|
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
|
+
}
|
|
29952
30127
|
if (typeof channel.sendKeyboard === "function") {
|
|
29953
30128
|
const isAuto = !current.backend && current.backend !== "off";
|
|
29954
30129
|
const isOff = current.backend === "off";
|
|
@@ -29996,7 +30171,14 @@ async function handleSummarizerCommand(chatId, commandArgs, msg, channel) {
|
|
|
29996
30171
|
}]);
|
|
29997
30172
|
}
|
|
29998
30173
|
}
|
|
29999
|
-
|
|
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);
|
|
30000
30182
|
} else {
|
|
30001
30183
|
await channel.sendText(chatId, `Summarizer: ${currentLabel}
|
|
30002
30184
|
|
|
@@ -31519,14 +31701,40 @@ ${value ? "Full tool inputs/results will be saved to ~/.cc-claw/logs/sessions/"
|
|
|
31519
31701
|
if (rest === "auto") {
|
|
31520
31702
|
clearSummarizer(chatId);
|
|
31521
31703
|
await channel.sendText(chatId, "Summarizer set to auto (uses active backend).", { parseMode: "plain" });
|
|
31522
|
-
} else if (rest === "
|
|
31523
|
-
|
|
31524
|
-
await channel.sendText(chatId, "
|
|
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" });
|
|
31525
31723
|
} else {
|
|
31526
|
-
const
|
|
31527
|
-
const
|
|
31724
|
+
const isOff = rest === "off";
|
|
31725
|
+
const bk = isOff ? "off" : rest.split(":")[0];
|
|
31726
|
+
const mdl = isOff ? null : rest.split(":").slice(1).join(":") || null;
|
|
31528
31727
|
setSummarizer(chatId, bk, mdl);
|
|
31529
|
-
|
|
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
|
+
}
|
|
31530
31738
|
}
|
|
31531
31739
|
} else if (data.startsWith("perms:")) {
|
|
31532
31740
|
let chosen = data.slice(6);
|
|
@@ -34525,6 +34733,8 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
|
|
|
34525
34733
|
return;
|
|
34526
34734
|
}
|
|
34527
34735
|
getTypingManager().acquire(chatId, channel);
|
|
34736
|
+
let stopDraftTimer = () => {
|
|
34737
|
+
};
|
|
34528
34738
|
try {
|
|
34529
34739
|
const tMode = settings.getMode();
|
|
34530
34740
|
const tVerbose = settings.getVerboseLevel();
|
|
@@ -34584,7 +34794,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
|
|
|
34584
34794
|
draftState.dirty = true;
|
|
34585
34795
|
};
|
|
34586
34796
|
}
|
|
34587
|
-
|
|
34797
|
+
stopDraftTimer = () => {
|
|
34588
34798
|
if (draftState?.flushTimer) {
|
|
34589
34799
|
clearInterval(draftState.flushTimer);
|
|
34590
34800
|
draftState.flushTimer = null;
|
|
@@ -34620,9 +34830,14 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
|
|
|
34620
34830
|
} catch {
|
|
34621
34831
|
}
|
|
34622
34832
|
},
|
|
34623
|
-
onCompaction: (cid) => {
|
|
34624
|
-
|
|
34625
|
-
|
|
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
|
+
}
|
|
34626
34841
|
},
|
|
34627
34842
|
onSlotRotation: (cid, from, to) => {
|
|
34628
34843
|
const slots = getGeminiSlots();
|
|
@@ -34640,7 +34855,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
|
|
|
34640
34855
|
});
|
|
34641
34856
|
}
|
|
34642
34857
|
});
|
|
34643
|
-
|
|
34858
|
+
stopDraftTimer();
|
|
34644
34859
|
const elapsedMs = Date.now() - sigT0;
|
|
34645
34860
|
const elapsedSec = (elapsedMs / 1e3).toFixed(1);
|
|
34646
34861
|
if (liveStatus && response.thinkingText?.trim()) {
|
|
@@ -36230,7 +36445,7 @@ var init_telegram2 = __esm({
|
|
|
36230
36445
|
};
|
|
36231
36446
|
}
|
|
36232
36447
|
async start(handler) {
|
|
36233
|
-
await this.bot.api.setMyCommands([
|
|
36448
|
+
await this.throttle.send(this.primaryChatId, "setMyCommands", () => this.bot.api.setMyCommands([
|
|
36234
36449
|
// Core
|
|
36235
36450
|
{ command: "menu", description: "Home screen \u2014 quick-access keyboard" },
|
|
36236
36451
|
{ command: "m", description: "Home screen (alias for /menu)" },
|
|
@@ -36308,7 +36523,7 @@ var init_telegram2 = __esm({
|
|
|
36308
36523
|
// Context & info
|
|
36309
36524
|
{ command: "info", description: "Current chat context (ID, topic, sender, settings)" },
|
|
36310
36525
|
{ command: "council", description: "Multi-model debate (select models, anonymous rounds)" }
|
|
36311
|
-
]);
|
|
36526
|
+
]));
|
|
36312
36527
|
this.bot.on("message", async (ctx) => {
|
|
36313
36528
|
const chatId = ctx.chat.id.toString();
|
|
36314
36529
|
const senderId = ctx.from?.id?.toString() ?? "";
|
|
@@ -36354,15 +36569,13 @@ var init_telegram2 = __esm({
|
|
|
36354
36569
|
const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
|
|
36355
36570
|
log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
|
|
36356
36571
|
if (!this.isAuthorized(userId) && !this.isAuthorized(chatId)) {
|
|
36357
|
-
ctx.answerCallbackQuery("Unauthorized")
|
|
36358
|
-
});
|
|
36572
|
+
this.throttle.tryBestEffort(chatId, "answerCallbackQuery:unauth", () => ctx.answerCallbackQuery("Unauthorized"));
|
|
36359
36573
|
return;
|
|
36360
36574
|
}
|
|
36361
36575
|
const data = ctx.callbackQuery.data;
|
|
36362
36576
|
const messageId = ctx.callbackQuery.message?.message_id?.toString();
|
|
36363
36577
|
const threadId = ctx.callbackQuery.message?.message_thread_id;
|
|
36364
|
-
|
|
36365
|
-
});
|
|
36578
|
+
this.throttle.tryBestEffort(chatId, "answerCallbackQuery", () => ctx.answerCallbackQuery());
|
|
36366
36579
|
(async () => {
|
|
36367
36580
|
let ch = this;
|
|
36368
36581
|
if (threadId) {
|
|
@@ -36411,7 +36624,7 @@ var init_telegram2 = __esm({
|
|
|
36411
36624
|
this.keepaliveInterval = setInterval(async () => {
|
|
36412
36625
|
if (!this.pollingExpected) return;
|
|
36413
36626
|
try {
|
|
36414
|
-
await this.bot.api.getMe();
|
|
36627
|
+
await this.throttle.tryBestEffort(this.primaryChatId, "getMe:keepalive", () => this.bot.api.getMe());
|
|
36415
36628
|
this.lastPollingCheckAt = Date.now();
|
|
36416
36629
|
} catch (err) {
|
|
36417
36630
|
error("[telegram] Keepalive ping failed:", err);
|
|
@@ -36551,7 +36764,7 @@ var init_telegram2 = __esm({
|
|
|
36551
36764
|
);
|
|
36552
36765
|
}
|
|
36553
36766
|
async downloadFile(fileId) {
|
|
36554
|
-
const file = await this.bot.api.getFile(fileId);
|
|
36767
|
+
const file = await this.throttle.send(this.primaryChatId, "getFile", () => this.bot.api.getFile(fileId));
|
|
36555
36768
|
const fileUrl = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${file.file_path}`;
|
|
36556
36769
|
const response = await fetch(fileUrl);
|
|
36557
36770
|
return Buffer.from(await response.arrayBuffer());
|
|
@@ -36840,7 +37053,11 @@ var init_telegram2 = __esm({
|
|
|
36840
37053
|
}
|
|
36841
37054
|
}
|
|
36842
37055
|
}
|
|
36843
|
-
await
|
|
37056
|
+
await this.throttle.tryBestEffort(
|
|
37057
|
+
this.primaryChatId,
|
|
37058
|
+
"answerInlineQuery",
|
|
37059
|
+
() => ctx.answerInlineQuery(results.slice(0, 10), { cache_time: 30 })
|
|
37060
|
+
);
|
|
36844
37061
|
}
|
|
36845
37062
|
trackAgentMessage(messageId, chatId) {
|
|
36846
37063
|
if (this.agentMessageIds.size >= 1e4) {
|
package/package.json
CHANGED