cc-claw 0.20.14 → 0.20.15
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 +502 -192
- 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.20.
|
|
36
|
+
VERSION = true ? "0.20.15" : (() => {
|
|
37
37
|
try {
|
|
38
38
|
return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
39
39
|
} catch {
|
|
@@ -1333,7 +1333,8 @@ function initSchema(db3) {
|
|
|
1333
1333
|
last_run_at TEXT,
|
|
1334
1334
|
next_run_at TEXT,
|
|
1335
1335
|
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
1336
|
-
allow_paid_slots INTEGER NOT NULL DEFAULT 0
|
|
1336
|
+
allow_paid_slots INTEGER NOT NULL DEFAULT 0,
|
|
1337
|
+
credential_slot_id INTEGER
|
|
1337
1338
|
);
|
|
1338
1339
|
`);
|
|
1339
1340
|
try {
|
|
@@ -1493,6 +1494,10 @@ function initSchema(db3) {
|
|
|
1493
1494
|
db3.exec("ALTER TABLE jobs ADD COLUMN allow_paid_slots INTEGER NOT NULL DEFAULT 0");
|
|
1494
1495
|
} catch {
|
|
1495
1496
|
}
|
|
1497
|
+
try {
|
|
1498
|
+
db3.exec("ALTER TABLE jobs ADD COLUMN credential_slot_id INTEGER");
|
|
1499
|
+
} catch {
|
|
1500
|
+
}
|
|
1496
1501
|
try {
|
|
1497
1502
|
db3.exec("UPDATE jobs SET job_type = 'reflection' WHERE description LIKE '%reflection analysis%' AND job_type = 'normal'");
|
|
1498
1503
|
} catch {
|
|
@@ -3317,8 +3322,8 @@ function insertJob(params) {
|
|
|
3317
3322
|
const db3 = getDb();
|
|
3318
3323
|
const result = db3.prepare(`
|
|
3319
3324
|
INSERT INTO jobs (schedule_type, cron, at_time, every_ms, title, description, chat_id,
|
|
3320
|
-
backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone, job_type, allow_paid_slots)
|
|
3321
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3325
|
+
backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone, job_type, allow_paid_slots, credential_slot_id)
|
|
3326
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3322
3327
|
`).run(
|
|
3323
3328
|
params.scheduleType,
|
|
3324
3329
|
params.cron ?? null,
|
|
@@ -3338,7 +3343,8 @@ function insertJob(params) {
|
|
|
3338
3343
|
params.deliveryMode ?? "announce",
|
|
3339
3344
|
params.timezone ?? "UTC",
|
|
3340
3345
|
params.jobType ?? "normal",
|
|
3341
|
-
params.allowPaidSlots ? 1 : 0
|
|
3346
|
+
params.allowPaidSlots ? 1 : 0,
|
|
3347
|
+
params.credentialSlotId ?? null
|
|
3342
3348
|
);
|
|
3343
3349
|
return getJobById(Number(result.lastInsertRowid));
|
|
3344
3350
|
}
|
|
@@ -3346,6 +3352,7 @@ function mapJobRow(row) {
|
|
|
3346
3352
|
if (!row) return void 0;
|
|
3347
3353
|
row.fallbacks = row.fallbacks ? JSON.parse(row.fallbacks) : [];
|
|
3348
3354
|
row.allowPaidSlots = !!row.allowPaidSlots;
|
|
3355
|
+
row.credentialSlotId = row.credentialSlotId ?? null;
|
|
3349
3356
|
return row;
|
|
3350
3357
|
}
|
|
3351
3358
|
function getJobById(id) {
|
|
@@ -3400,7 +3407,8 @@ function updateJob(id, fields) {
|
|
|
3400
3407
|
deliveryMode: "delivery_mode",
|
|
3401
3408
|
timezone: "timezone",
|
|
3402
3409
|
jobType: "job_type",
|
|
3403
|
-
allowPaidSlots: "allow_paid_slots"
|
|
3410
|
+
allowPaidSlots: "allow_paid_slots",
|
|
3411
|
+
credentialSlotId: "credential_slot_id"
|
|
3404
3412
|
};
|
|
3405
3413
|
for (const [key, val] of Object.entries(fields)) {
|
|
3406
3414
|
const col = fieldMap[key];
|
|
@@ -3473,7 +3481,7 @@ var init_jobs = __esm({
|
|
|
3473
3481
|
session_type as sessionType, channel, target, delivery_mode as deliveryMode,
|
|
3474
3482
|
timezone, job_type as jobType, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
|
|
3475
3483
|
next_run_at as nextRunAt, consecutive_failures as consecutiveFailures,
|
|
3476
|
-
allow_paid_slots as allowPaidSlots
|
|
3484
|
+
allow_paid_slots as allowPaidSlots, credential_slot_id as credentialSlotId
|
|
3477
3485
|
FROM jobs
|
|
3478
3486
|
`;
|
|
3479
3487
|
}
|
|
@@ -4141,6 +4149,24 @@ var init_store5 = __esm({
|
|
|
4141
4149
|
}
|
|
4142
4150
|
});
|
|
4143
4151
|
|
|
4152
|
+
// src/backends/types.ts
|
|
4153
|
+
function isBackendId(value) {
|
|
4154
|
+
return Object.values(BACKEND).includes(value);
|
|
4155
|
+
}
|
|
4156
|
+
var BACKEND;
|
|
4157
|
+
var init_types = __esm({
|
|
4158
|
+
"src/backends/types.ts"() {
|
|
4159
|
+
"use strict";
|
|
4160
|
+
BACKEND = {
|
|
4161
|
+
CLAUDE: "claude",
|
|
4162
|
+
GEMINI: "gemini",
|
|
4163
|
+
CODEX: "codex",
|
|
4164
|
+
CURSOR: "cursor",
|
|
4165
|
+
OLLAMA: "ollama"
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
});
|
|
4169
|
+
|
|
4144
4170
|
// src/env.ts
|
|
4145
4171
|
import { homedir as homedir2 } from "os";
|
|
4146
4172
|
function stripProxyVars(env) {
|
|
@@ -4223,6 +4249,11 @@ var init_strip_thinking = __esm({
|
|
|
4223
4249
|
});
|
|
4224
4250
|
|
|
4225
4251
|
// src/backends/resolve-executable.ts
|
|
4252
|
+
var resolve_executable_exports = {};
|
|
4253
|
+
__export(resolve_executable_exports, {
|
|
4254
|
+
clearExecutableCache: () => clearExecutableCache,
|
|
4255
|
+
resolveExecutable: () => resolveExecutable
|
|
4256
|
+
});
|
|
4226
4257
|
import { existsSync as existsSync2 } from "fs";
|
|
4227
4258
|
import { spawnSync } from "child_process";
|
|
4228
4259
|
function resolveExecutable(config2) {
|
|
@@ -4259,6 +4290,9 @@ function resolveExecutable(config2) {
|
|
|
4259
4290
|
cache.set(config2.binaryName, fallback);
|
|
4260
4291
|
return fallback;
|
|
4261
4292
|
}
|
|
4293
|
+
function clearExecutableCache() {
|
|
4294
|
+
cache.clear();
|
|
4295
|
+
}
|
|
4262
4296
|
var cache;
|
|
4263
4297
|
var init_resolve_executable = __esm({
|
|
4264
4298
|
"src/backends/resolve-executable.ts"() {
|
|
@@ -6494,24 +6528,6 @@ var init_ollama2 = __esm({
|
|
|
6494
6528
|
}
|
|
6495
6529
|
});
|
|
6496
6530
|
|
|
6497
|
-
// src/backends/types.ts
|
|
6498
|
-
function isBackendId(value) {
|
|
6499
|
-
return Object.values(BACKEND).includes(value);
|
|
6500
|
-
}
|
|
6501
|
-
var BACKEND;
|
|
6502
|
-
var init_types = __esm({
|
|
6503
|
-
"src/backends/types.ts"() {
|
|
6504
|
-
"use strict";
|
|
6505
|
-
BACKEND = {
|
|
6506
|
-
CLAUDE: "claude",
|
|
6507
|
-
GEMINI: "gemini",
|
|
6508
|
-
CODEX: "codex",
|
|
6509
|
-
CURSOR: "cursor",
|
|
6510
|
-
OLLAMA: "ollama"
|
|
6511
|
-
};
|
|
6512
|
-
}
|
|
6513
|
-
});
|
|
6514
|
-
|
|
6515
6531
|
// src/backends/index.ts
|
|
6516
6532
|
var backends_exports = {};
|
|
6517
6533
|
__export(backends_exports, {
|
|
@@ -14978,8 +14994,8 @@ var init_telegram_throttle = __esm({
|
|
|
14978
14994
|
"src/channels/telegram-throttle.ts"() {
|
|
14979
14995
|
"use strict";
|
|
14980
14996
|
init_log();
|
|
14981
|
-
PER_CHAT_INTERVAL_MS =
|
|
14982
|
-
GLOBAL_INTERVAL_MS =
|
|
14997
|
+
PER_CHAT_INTERVAL_MS = 1e3;
|
|
14998
|
+
GLOBAL_INTERVAL_MS = 100;
|
|
14983
14999
|
MAX_RETRIES2 = 2;
|
|
14984
15000
|
RETRY_DELAY_MS = 1e3;
|
|
14985
15001
|
MAX_QUEUE_SIZE = 100;
|
|
@@ -15007,6 +15023,9 @@ var init_telegram_throttle = __esm({
|
|
|
15007
15023
|
}
|
|
15008
15024
|
/** Enqueue a Telegram API call with automatic pacing and 429 handling. */
|
|
15009
15025
|
async send(chatId, label2, fn) {
|
|
15026
|
+
if (this.isPaused() && label2.startsWith("editText")) {
|
|
15027
|
+
throw new Error("Throttle paused (rate limit active) \u2014 edit skipped");
|
|
15028
|
+
}
|
|
15010
15029
|
return new Promise((resolve, reject) => {
|
|
15011
15030
|
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
15012
15031
|
const dropped = this.queue.shift();
|
|
@@ -15019,6 +15038,31 @@ var init_telegram_throttle = __esm({
|
|
|
15019
15038
|
this.drain();
|
|
15020
15039
|
});
|
|
15021
15040
|
}
|
|
15041
|
+
/**
|
|
15042
|
+
* Best-effort send — drops silently if throttle is paused or queue is pressured.
|
|
15043
|
+
* Used for cosmetic calls (typing indicators, reactions) that should count toward
|
|
15044
|
+
* rate limits but must never queue up or amplify 429 spirals.
|
|
15045
|
+
*/
|
|
15046
|
+
async tryBestEffort(chatId, label2, fn) {
|
|
15047
|
+
if (this.isPaused()) return void 0;
|
|
15048
|
+
if (this.queue.length > 10) return void 0;
|
|
15049
|
+
const lastChat = this.lastSendPerChat.get(chatId) ?? 0;
|
|
15050
|
+
if (Date.now() - lastChat < PER_CHAT_INTERVAL_MS) return void 0;
|
|
15051
|
+
if (Date.now() - this.lastGlobalSend < GLOBAL_INTERVAL_MS) return void 0;
|
|
15052
|
+
try {
|
|
15053
|
+
const result = await fn();
|
|
15054
|
+
this.recordSend(chatId);
|
|
15055
|
+
return result;
|
|
15056
|
+
} catch (err) {
|
|
15057
|
+
if (is429(err)) {
|
|
15058
|
+
const retrySec = err.parameters?.retry_after ?? 10;
|
|
15059
|
+
this.pausedUntil = Date.now() + retrySec * 1e3;
|
|
15060
|
+
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
15061
|
+
warn(`[throttle] Best-effort ${label2} hit 429, pausing for ${retrySec}s`);
|
|
15062
|
+
}
|
|
15063
|
+
return void 0;
|
|
15064
|
+
}
|
|
15065
|
+
}
|
|
15022
15066
|
/** Check whether the throttle is currently paused (rate-limited). */
|
|
15023
15067
|
isPaused() {
|
|
15024
15068
|
return Date.now() < this.pausedUntil;
|
|
@@ -15101,12 +15145,13 @@ var init_telegram_throttle = __esm({
|
|
|
15101
15145
|
// ── Pause management ────────────────────────────────────────────────
|
|
15102
15146
|
enterPause(retrySec, failedItem) {
|
|
15103
15147
|
this.queue.unshift(failedItem);
|
|
15104
|
-
|
|
15148
|
+
const bufferedSec = Math.ceil(retrySec * 1.5);
|
|
15149
|
+
this.pausedUntil = Date.now() + bufferedSec * 1e3;
|
|
15105
15150
|
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
15106
15151
|
for (const qi of this.queue) {
|
|
15107
15152
|
this.chatsPendingNotification.add(qi.chatId);
|
|
15108
15153
|
}
|
|
15109
|
-
warn(`[throttle] 429 \u2014 pausing ALL sends for ${retrySec}s
|
|
15154
|
+
warn(`[throttle] 429 \u2014 pausing ALL sends for ${bufferedSec}s (retry_after=${retrySec}s + 50% buffer, ${this.queue.length} items queued)`);
|
|
15110
15155
|
}
|
|
15111
15156
|
async sendResumeNotifications() {
|
|
15112
15157
|
const chats2 = new Set(this.chatsPendingNotification);
|
|
@@ -15739,6 +15784,7 @@ var init_profile = __esm({
|
|
|
15739
15784
|
var classify_exports = {};
|
|
15740
15785
|
__export(classify_exports, {
|
|
15741
15786
|
classifyIntent: () => classifyIntent,
|
|
15787
|
+
classifyIntentAsync: () => classifyIntentAsync,
|
|
15742
15788
|
getIntentStats: () => getIntentStats,
|
|
15743
15789
|
resetIntentStats: () => resetIntentStats
|
|
15744
15790
|
});
|
|
@@ -15749,12 +15795,17 @@ function resetIntentStats() {
|
|
|
15749
15795
|
intentCounts.chat = 0;
|
|
15750
15796
|
intentCounts.agentic = 0;
|
|
15751
15797
|
}
|
|
15752
|
-
function
|
|
15798
|
+
function classifyIntentFast(text, chatId) {
|
|
15753
15799
|
const trimmed = text.trim();
|
|
15754
15800
|
if (trimmed.startsWith(">>")) return "agentic";
|
|
15755
15801
|
if (trimmed.startsWith("/")) return "agentic";
|
|
15756
15802
|
const lower = trimmed.toLowerCase();
|
|
15757
15803
|
const normalized = trimmed.replace(/^["'\u201C\u201D\u2018\u2019`\s]+|["'\u201C\u201D\u2018\u2019`\s]+$/g, "");
|
|
15804
|
+
const lowerNoPunct = lower.replace(/[?!.,…]+$/, "").trim();
|
|
15805
|
+
if (CHAT_EXACT.has(lowerNoPunct) || CHAT_EXACT.has(lower)) {
|
|
15806
|
+
log(`[intent] "${lower}" -> chat (exact match)`);
|
|
15807
|
+
return "chat";
|
|
15808
|
+
}
|
|
15758
15809
|
const sessionId = getSessionId(chatId);
|
|
15759
15810
|
if (sessionId) {
|
|
15760
15811
|
const lastTs = getLastMessageTimestamp(chatId);
|
|
@@ -15762,47 +15813,148 @@ function classifyIntent(text, chatId) {
|
|
|
15762
15813
|
const elapsed = Date.now() - lastTs;
|
|
15763
15814
|
if (elapsed < 12e4 && trimmed.length < 30) {
|
|
15764
15815
|
log(`[intent] "${trimmed.slice(0, 30)}" -> agentic (active session, ${(elapsed / 1e3).toFixed(0)}s ago)`);
|
|
15765
|
-
intentCounts.agentic++;
|
|
15766
15816
|
return "agentic";
|
|
15767
15817
|
}
|
|
15768
15818
|
}
|
|
15769
15819
|
}
|
|
15770
|
-
if (CHAT_EXACT.has(lower)) {
|
|
15771
|
-
log(`[intent] "${lower}" -> chat (exact match)`);
|
|
15772
|
-
intentCounts.chat++;
|
|
15773
|
-
return "chat";
|
|
15774
|
-
}
|
|
15775
15820
|
if (trimmed.length <= 4 && /^[\p{Emoji}\s]+$/u.test(trimmed)) {
|
|
15776
15821
|
log(`[intent] "${trimmed}" -> chat (emoji-only)`);
|
|
15777
|
-
intentCounts.chat++;
|
|
15778
15822
|
return "chat";
|
|
15779
15823
|
}
|
|
15780
15824
|
for (const pattern of STRUCTURAL_PATTERNS) {
|
|
15781
15825
|
if (pattern.test(normalized)) {
|
|
15782
15826
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (structural: ${pattern})`);
|
|
15783
|
-
intentCounts.agentic++;
|
|
15784
15827
|
return "agentic";
|
|
15785
15828
|
}
|
|
15786
15829
|
}
|
|
15787
15830
|
for (const pattern of MUTATION_PATTERNS) {
|
|
15788
15831
|
if (pattern.test(normalized)) {
|
|
15789
15832
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (mutation: ${pattern})`);
|
|
15790
|
-
intentCounts.agentic++;
|
|
15791
15833
|
return "agentic";
|
|
15792
15834
|
}
|
|
15793
15835
|
}
|
|
15794
15836
|
for (const pattern of CHAT_QUESTION_PATTERNS) {
|
|
15795
15837
|
if (pattern.test(normalized)) {
|
|
15796
15838
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> chat (question: ${pattern})`);
|
|
15797
|
-
intentCounts.chat++;
|
|
15798
15839
|
return "chat";
|
|
15799
15840
|
}
|
|
15800
15841
|
}
|
|
15801
|
-
|
|
15842
|
+
return null;
|
|
15843
|
+
}
|
|
15844
|
+
async function classifyWithLlm(text) {
|
|
15845
|
+
try {
|
|
15846
|
+
const ollamaResult = await classifyWithOllama(text);
|
|
15847
|
+
if (ollamaResult) return ollamaResult;
|
|
15848
|
+
} catch {
|
|
15849
|
+
}
|
|
15850
|
+
try {
|
|
15851
|
+
const cliResult = await classifyWithSummarizerCli(text);
|
|
15852
|
+
if (cliResult) return cliResult;
|
|
15853
|
+
} catch {
|
|
15854
|
+
}
|
|
15855
|
+
return null;
|
|
15856
|
+
}
|
|
15857
|
+
async function classifyWithOllama(text) {
|
|
15858
|
+
const ollamaService = await Promise.resolve().then(() => (init_service(), service_exports));
|
|
15859
|
+
const ollamaClient = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
15860
|
+
const servers = ollamaService.listServers();
|
|
15861
|
+
const onlineServer = servers.find((s) => s.status === "online");
|
|
15862
|
+
if (!onlineServer) return null;
|
|
15863
|
+
const models = ollamaService.listModels(onlineServer.name);
|
|
15864
|
+
if (models.length === 0) return null;
|
|
15865
|
+
const sorted = [...models].sort(
|
|
15866
|
+
(a, b) => (a.sizeBytes ?? Infinity) - (b.sizeBytes ?? Infinity)
|
|
15867
|
+
);
|
|
15868
|
+
const model2 = sorted[0].name;
|
|
15869
|
+
const result = await ollamaClient.chat(
|
|
15870
|
+
onlineServer.baseUrl,
|
|
15871
|
+
model2,
|
|
15872
|
+
[{ role: "user", content: LLM_CLASSIFY_PROMPT + text.slice(0, 500) }],
|
|
15873
|
+
{ timeoutMs: LLM_CLASSIFY_TIMEOUT_MS, maxTokens: 5, temperature: 0 }
|
|
15874
|
+
);
|
|
15875
|
+
return parseClassifyResponse(result.text);
|
|
15876
|
+
}
|
|
15877
|
+
async function classifyWithSummarizerCli(text) {
|
|
15878
|
+
const { getSummarizer: getSummarizer3 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
|
|
15879
|
+
const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
15880
|
+
const config2 = getSummarizer3("__global__");
|
|
15881
|
+
let backendId = config2.backend;
|
|
15882
|
+
let modelName = config2.model;
|
|
15883
|
+
if (!backendId) {
|
|
15884
|
+
const adapters2 = getAllAdapters5();
|
|
15885
|
+
const cheapAdapter = adapters2.find((a) => a.summarizerModel);
|
|
15886
|
+
if (!cheapAdapter) return null;
|
|
15887
|
+
backendId = cheapAdapter.id;
|
|
15888
|
+
modelName = cheapAdapter.summarizerModel;
|
|
15889
|
+
}
|
|
15890
|
+
const adapter = getAdapter4(backendId);
|
|
15891
|
+
if (!adapter) return null;
|
|
15892
|
+
const model2 = modelName ?? adapter.summarizerModel;
|
|
15893
|
+
const { spawn: spawn8 } = await import("child_process");
|
|
15894
|
+
const { resolveExecutable: resolveExecutable4 } = await Promise.resolve().then(() => (init_resolve_executable(), resolve_executable_exports));
|
|
15895
|
+
const exe = resolveExecutable4(adapter.id);
|
|
15896
|
+
if (!exe) return null;
|
|
15897
|
+
return new Promise((resolve) => {
|
|
15898
|
+
const timeout = setTimeout(() => {
|
|
15899
|
+
proc.kill("SIGKILL");
|
|
15900
|
+
resolve(null);
|
|
15901
|
+
}, LLM_CLASSIFY_TIMEOUT_MS);
|
|
15902
|
+
const args = adapter.id === "claude" ? ["-p", LLM_CLASSIFY_PROMPT + text.slice(0, 500), "--model", model2, "--no-input"] : ["-p", LLM_CLASSIFY_PROMPT + text.slice(0, 500), "--model", model2];
|
|
15903
|
+
const proc = spawn8(exe, args, {
|
|
15904
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
15905
|
+
timeout: LLM_CLASSIFY_TIMEOUT_MS + 1e3
|
|
15906
|
+
});
|
|
15907
|
+
let output2 = "";
|
|
15908
|
+
proc.stdout?.on("data", (chunk) => {
|
|
15909
|
+
output2 += chunk.toString();
|
|
15910
|
+
});
|
|
15911
|
+
proc.on("close", () => {
|
|
15912
|
+
clearTimeout(timeout);
|
|
15913
|
+
resolve(parseClassifyResponse(output2));
|
|
15914
|
+
});
|
|
15915
|
+
proc.on("error", () => {
|
|
15916
|
+
clearTimeout(timeout);
|
|
15917
|
+
resolve(null);
|
|
15918
|
+
});
|
|
15919
|
+
});
|
|
15920
|
+
}
|
|
15921
|
+
function parseClassifyResponse(text) {
|
|
15922
|
+
const lower = text.trim().toLowerCase();
|
|
15923
|
+
if (lower.includes("chat")) return "chat";
|
|
15924
|
+
if (lower.includes("task")) return "agentic";
|
|
15925
|
+
return null;
|
|
15926
|
+
}
|
|
15927
|
+
function classifyIntent(text, chatId) {
|
|
15928
|
+
const fast = classifyIntentFast(text, chatId);
|
|
15929
|
+
if (fast) {
|
|
15930
|
+
intentCounts[fast]++;
|
|
15931
|
+
return fast;
|
|
15932
|
+
}
|
|
15933
|
+
log(`[intent] "${text.slice(0, 40)}..." -> agentic (default)`);
|
|
15934
|
+
intentCounts.agentic++;
|
|
15935
|
+
return "agentic";
|
|
15936
|
+
}
|
|
15937
|
+
async function classifyIntentAsync(text, chatId) {
|
|
15938
|
+
const fast = classifyIntentFast(text, chatId);
|
|
15939
|
+
if (fast) {
|
|
15940
|
+
intentCounts[fast]++;
|
|
15941
|
+
return fast;
|
|
15942
|
+
}
|
|
15943
|
+
try {
|
|
15944
|
+
const llmResult = await classifyWithLlm(text);
|
|
15945
|
+
if (llmResult) {
|
|
15946
|
+
log(`[intent] "${text.slice(0, 40)}..." -> ${llmResult} (LLM)`);
|
|
15947
|
+
intentCounts[llmResult]++;
|
|
15948
|
+
return llmResult;
|
|
15949
|
+
}
|
|
15950
|
+
} catch (err) {
|
|
15951
|
+
warn(`[intent] LLM classification failed: ${err instanceof Error ? err.message : err}`);
|
|
15952
|
+
}
|
|
15953
|
+
log(`[intent] "${text.slice(0, 40)}..." -> agentic (default, LLM unavailable)`);
|
|
15802
15954
|
intentCounts.agentic++;
|
|
15803
15955
|
return "agentic";
|
|
15804
15956
|
}
|
|
15805
|
-
var intentCounts, CHAT_EXACT, MUTATION_PATTERNS, CHAT_QUESTION_PATTERNS, STRUCTURAL_PATTERNS;
|
|
15957
|
+
var intentCounts, CHAT_EXACT, MUTATION_PATTERNS, CHAT_QUESTION_PATTERNS, STRUCTURAL_PATTERNS, LLM_CLASSIFY_PROMPT, LLM_CLASSIFY_TIMEOUT_MS;
|
|
15806
15958
|
var init_classify = __esm({
|
|
15807
15959
|
"src/intent/classify.ts"() {
|
|
15808
15960
|
"use strict";
|
|
@@ -15898,6 +16050,13 @@ var init_classify = __esm({
|
|
|
15898
16050
|
/\b(error|bug|crash|fail|broken|issue|problem|exception|stack\s?trace)\b/i,
|
|
15899
16051
|
/\b(function|class|const|let|var|import|export|return|async|await)\b/i
|
|
15900
16052
|
];
|
|
16053
|
+
LLM_CLASSIFY_PROMPT = `Classify this message as either "chat" or "task".
|
|
16054
|
+
- "chat" = greeting, small talk, acknowledgment, question, opinion, or anything conversational
|
|
16055
|
+
- "task" = request to DO something (research, draft, analyze, code, create, fix, etc.)
|
|
16056
|
+
Reply with ONLY the word "chat" or "task", nothing else.
|
|
16057
|
+
|
|
16058
|
+
Message: `;
|
|
16059
|
+
LLM_CLASSIFY_TIMEOUT_MS = 3e3;
|
|
15901
16060
|
}
|
|
15902
16061
|
});
|
|
15903
16062
|
|
|
@@ -16124,8 +16283,8 @@ async function handleWizardText(chatId, text, channel) {
|
|
|
16124
16283
|
case "thinking": {
|
|
16125
16284
|
const level = text.trim().toLowerCase();
|
|
16126
16285
|
pending.thinking = level || "auto";
|
|
16127
|
-
pending.step = "
|
|
16128
|
-
await
|
|
16286
|
+
pending.step = "account";
|
|
16287
|
+
await promptAccount(chatId, channel);
|
|
16129
16288
|
break;
|
|
16130
16289
|
}
|
|
16131
16290
|
case "timeout": {
|
|
@@ -16218,6 +16377,17 @@ async function handleWizardCallback(chatId, data, channel) {
|
|
|
16218
16377
|
await promptThinking(chatId, channel);
|
|
16219
16378
|
} else if (data.startsWith("sched:thinking:")) {
|
|
16220
16379
|
pending.thinking = data.slice(15);
|
|
16380
|
+
pending.step = "account";
|
|
16381
|
+
await promptAccount(chatId, channel);
|
|
16382
|
+
} else if (data.startsWith("sched:slot:")) {
|
|
16383
|
+
const val = data.slice(11);
|
|
16384
|
+
if (val === "auto") {
|
|
16385
|
+
pending.credentialSlotId = null;
|
|
16386
|
+
} else {
|
|
16387
|
+
pending.credentialSlotId = parseInt(val, 10);
|
|
16388
|
+
}
|
|
16389
|
+
const slotLabel = resolveSlotLabel(pending.backend, pending.credentialSlotId);
|
|
16390
|
+
await channel.sendText(chatId, `Account: ${slotLabel}`, { parseMode: "plain" });
|
|
16221
16391
|
pending.step = "timeout";
|
|
16222
16392
|
await promptTimeout(chatId, channel);
|
|
16223
16393
|
} else if (data.startsWith("sched:timeout:")) {
|
|
@@ -16323,9 +16493,52 @@ async function promptThinking(chatId, channel) {
|
|
|
16323
16493
|
await channel.sendKeyboard(chatId, "Thinking/effort level for this job?", buttons);
|
|
16324
16494
|
} else {
|
|
16325
16495
|
pending.thinking = "auto";
|
|
16496
|
+
pending.step = "account";
|
|
16497
|
+
await promptAccount(chatId, channel);
|
|
16498
|
+
}
|
|
16499
|
+
}
|
|
16500
|
+
async function promptAccount(chatId, channel) {
|
|
16501
|
+
const pending = pendingJobs.get(chatId);
|
|
16502
|
+
if (!pending?.backend) {
|
|
16503
|
+
if (pending) pending.step = "timeout";
|
|
16504
|
+
await promptTimeout(chatId, channel);
|
|
16505
|
+
return;
|
|
16506
|
+
}
|
|
16507
|
+
const isGemini = pending.backend === BACKEND.GEMINI;
|
|
16508
|
+
const slots = isGemini ? getGeminiSlots() : getBackendSlots(pending.backend);
|
|
16509
|
+
const enabledSlots = slots.filter((s) => s.enabled);
|
|
16510
|
+
if (enabledSlots.length === 0) {
|
|
16511
|
+
pending.credentialSlotId = null;
|
|
16326
16512
|
pending.step = "timeout";
|
|
16327
16513
|
await promptTimeout(chatId, channel);
|
|
16514
|
+
return;
|
|
16515
|
+
}
|
|
16516
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
16517
|
+
await channel.sendText(chatId, `Enter account slot ID, or "auto" for rotation:`, { parseMode: "plain" });
|
|
16518
|
+
return;
|
|
16328
16519
|
}
|
|
16520
|
+
const buttons = [
|
|
16521
|
+
[{ label: "\u{1F504} Auto (rotate)", data: "sched:slot:auto" }]
|
|
16522
|
+
];
|
|
16523
|
+
for (const slot of enabledSlots) {
|
|
16524
|
+
const s = slot;
|
|
16525
|
+
const icon = s.slotType === "api_key" ? "\u{1F511}" : "\u{1F4E7}";
|
|
16526
|
+
const label2 = s.label || s.email || `Slot #${s.id}`;
|
|
16527
|
+
const type = s.slotType === "api_key" ? "API key" : "OAuth";
|
|
16528
|
+
buttons.push([{ label: `${icon} ${label2} (${type})`, data: `sched:slot:${s.id}` }]);
|
|
16529
|
+
}
|
|
16530
|
+
await channel.sendKeyboard(chatId, "Which account should this job use?", buttons);
|
|
16531
|
+
}
|
|
16532
|
+
function resolveSlotLabel(backend2, slotId) {
|
|
16533
|
+
if (!slotId || !backend2) return "Auto (rotate)";
|
|
16534
|
+
const isGemini = backend2 === BACKEND.GEMINI;
|
|
16535
|
+
const slots = isGemini ? getGeminiSlots() : getBackendSlots(backend2);
|
|
16536
|
+
const slot = slots.find((s) => s.id === slotId);
|
|
16537
|
+
if (!slot) return `Slot #${slotId} (unknown)`;
|
|
16538
|
+
const icon = slot.slotType === "api_key" ? "\u{1F511}" : "\u{1F4E7}";
|
|
16539
|
+
const label2 = slot.label || slot.email || `Slot #${slot.id}`;
|
|
16540
|
+
const type = slot.slotType === "api_key" ? "API key" : "OAuth";
|
|
16541
|
+
return `${icon} ${label2} (${type})`;
|
|
16329
16542
|
}
|
|
16330
16543
|
async function promptTimeout(chatId, channel) {
|
|
16331
16544
|
if (typeof channel.sendKeyboard !== "function") {
|
|
@@ -16380,6 +16593,7 @@ async function promptConfirm(chatId, channel) {
|
|
|
16380
16593
|
if (!pending) return;
|
|
16381
16594
|
const backendName = pending.backend ? getAdapter(pending.backend).displayName : "default";
|
|
16382
16595
|
const timeoutLabel = pending.timeout ? `${pending.timeout}s (${Math.round(pending.timeout / 60)} min)` : `Default (${TIMEOUT_DEFAULT_SECONDS}s)`;
|
|
16596
|
+
const accountLabel = resolveSlotLabel(pending.backend, pending.credentialSlotId);
|
|
16383
16597
|
const lines = [
|
|
16384
16598
|
"Job configuration:",
|
|
16385
16599
|
"",
|
|
@@ -16388,6 +16602,7 @@ async function promptConfirm(chatId, channel) {
|
|
|
16388
16602
|
` Timezone: ${pending.timezone ?? "UTC"}`,
|
|
16389
16603
|
` Backend: ${backendName}`,
|
|
16390
16604
|
` Model: ${pending.model ?? "default"}`,
|
|
16605
|
+
` Account: ${accountLabel}`,
|
|
16391
16606
|
` Thinking: ${pending.thinking ?? "auto"}`,
|
|
16392
16607
|
` Timeout: ${timeoutLabel}`,
|
|
16393
16608
|
` Session: ${pending.sessionType ?? "isolated"}`,
|
|
@@ -16445,7 +16660,8 @@ ${pending.task}`, {
|
|
|
16445
16660
|
channel: pending.channel ?? null,
|
|
16446
16661
|
target: pending.target ?? null,
|
|
16447
16662
|
deliveryMode: pending.deliveryMode ?? "announce",
|
|
16448
|
-
timezone: pending.timezone ?? "UTC"
|
|
16663
|
+
timezone: pending.timezone ?? "UTC",
|
|
16664
|
+
credentialSlotId: pending.credentialSlotId ?? null
|
|
16449
16665
|
};
|
|
16450
16666
|
try {
|
|
16451
16667
|
if (editJobId) {
|
|
@@ -16507,7 +16723,8 @@ async function startEditWizard(chatId, jobId, channel) {
|
|
|
16507
16723
|
sessionType: job.sessionType,
|
|
16508
16724
|
deliveryMode: job.deliveryMode,
|
|
16509
16725
|
channel: job.channel ?? void 0,
|
|
16510
|
-
target: job.target ?? void 0
|
|
16726
|
+
target: job.target ?? void 0,
|
|
16727
|
+
credentialSlotId: job.credentialSlotId ?? void 0
|
|
16511
16728
|
};
|
|
16512
16729
|
if (pendingJobs.has(chatId)) cancelWizard(chatId);
|
|
16513
16730
|
pendingJobs.set(chatId, pending);
|
|
@@ -17273,10 +17490,11 @@ var init_live_status = __esm({
|
|
|
17273
17490
|
"use strict";
|
|
17274
17491
|
init_log();
|
|
17275
17492
|
init_helpers();
|
|
17276
|
-
|
|
17277
|
-
|
|
17493
|
+
init_telegram_throttle();
|
|
17494
|
+
FLUSH_INTERVAL_DM_MS = 2e3;
|
|
17495
|
+
FLUSH_INTERVAL_GROUP_MS = 5e3;
|
|
17278
17496
|
MAX_THINKING_CHARS = 800;
|
|
17279
|
-
GLOBAL_MIN_GAP_MS =
|
|
17497
|
+
GLOBAL_MIN_GAP_MS = 1e3;
|
|
17280
17498
|
globalLastFlushAt = 0;
|
|
17281
17499
|
TRIM_THRESHOLD = 3500;
|
|
17282
17500
|
MAX_ENTRIES = 200;
|
|
@@ -17382,6 +17600,8 @@ var init_live_status = __esm({
|
|
|
17382
17600
|
if (this.consecutiveEditFailures >= _LiveStatusMessage.MAX_EDIT_FAILURES) return;
|
|
17383
17601
|
if (Date.now() < this.nextFlushAllowedAt) return;
|
|
17384
17602
|
if (!canFlushGlobally()) return;
|
|
17603
|
+
const throttleState = getThrottleState();
|
|
17604
|
+
if (throttleState?.isPaused) return;
|
|
17385
17605
|
const deduped = dedupThinking(this.entries);
|
|
17386
17606
|
const body = renderEntries(deduped, this.modelLabel, Date.now() - this.startTime, this.hasTrimmed);
|
|
17387
17607
|
if (body === this.lastRendered) return;
|
|
@@ -19850,15 +20070,20 @@ async function sendJobDetail(chatId, jobId, channel, messageId) {
|
|
|
19850
20070
|
\u26A0\uFE0F ${job.consecutiveFailures} consecutive failures` : "";
|
|
19851
20071
|
const runs = getJobRuns(jobId, 1);
|
|
19852
20072
|
const lastRunStatus = runs.length > 0 ? runs[0].status : null;
|
|
20073
|
+
const thinking2 = job.thinking ?? "auto";
|
|
20074
|
+
const accountLabel = resolveSlotLabel(job.backend ?? void 0, job.credentialSlotId);
|
|
19853
20075
|
const lines = [
|
|
19854
20076
|
`Job #${job.id}: ${job.title ?? job.description}`,
|
|
19855
20077
|
buildSectionHeader("", 22),
|
|
19856
20078
|
...job.title ? [`Task: ${job.description}`] : [],
|
|
19857
20079
|
`Runs: ${schedule2}${tz}`,
|
|
19858
|
-
|
|
20080
|
+
"",
|
|
19859
20081
|
`Last run: ${lastRun}${lastRunStatus ? ` (${lastRunStatus})` : ""}`,
|
|
19860
20082
|
`Next run: ${nextRun}`,
|
|
19861
|
-
`Status: ${status}${failures}
|
|
20083
|
+
`Status: ${status}${failures}`,
|
|
20084
|
+
"",
|
|
20085
|
+
`\u{1F3F7} ${backend2} \xB7 ${model2} \xB7 ${thinking2}`,
|
|
20086
|
+
`\u{1F464} ${accountLabel}`
|
|
19862
20087
|
];
|
|
19863
20088
|
const text = lines.join("\n");
|
|
19864
20089
|
if (typeof channel.sendKeyboard !== "function") {
|
|
@@ -20071,6 +20296,7 @@ var init_ui = __esm({
|
|
|
20071
20296
|
init_format_time();
|
|
20072
20297
|
init_cron();
|
|
20073
20298
|
init_humanize();
|
|
20299
|
+
init_wizard();
|
|
20074
20300
|
init_stt();
|
|
20075
20301
|
init_helpers();
|
|
20076
20302
|
ROTATION_MODE_LABELS = {
|
|
@@ -25937,7 +26163,7 @@ async function handleText(msg, channel) {
|
|
|
25937
26163
|
await channel.sendText(chatId, limitMsg, { parseMode: "plain" });
|
|
25938
26164
|
return;
|
|
25939
26165
|
}
|
|
25940
|
-
let intent =
|
|
26166
|
+
let intent = await classifyIntentAsync(text, chatId);
|
|
25941
26167
|
const cleanText = text.startsWith(">>") ? text.slice(2).trim() : text;
|
|
25942
26168
|
let bootstrapTier = intent === "chat" ? "chat" : void 0;
|
|
25943
26169
|
let maxTurns = void 0;
|
|
@@ -25981,9 +26207,10 @@ async function handleText(msg, channel) {
|
|
|
25981
26207
|
agentMode: effectiveAgentMode
|
|
25982
26208
|
});
|
|
25983
26209
|
if (response.text) {
|
|
25984
|
-
|
|
26210
|
+
const revisedPlan = response.text.replace(/\[REACT:.+?\]/g, "").replace(/\[SEND_FILE:.+?\]/g, "").replace(/\[GENERATE_IMAGE:.+?\]/g, "").replace(/\[HISTORY_SEARCH:[^\]]+\]/g, "").trim();
|
|
26211
|
+
storePendingPlan(chatId, revisedPlan, text);
|
|
25985
26212
|
if (typeof channel.sendKeyboard === "function") {
|
|
25986
|
-
await channel.sendKeyboard(chatId, `\u{1F50D} ${
|
|
26213
|
+
await channel.sendKeyboard(chatId, `\u{1F50D} ${revisedPlan}`, [
|
|
25987
26214
|
[
|
|
25988
26215
|
{ label: "\u2705 Approve", data: "exec:approve", style: "success" },
|
|
25989
26216
|
{ label: "\u274C Reject", data: "exec:reject", style: "danger" }
|
|
@@ -26119,7 +26346,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26119
26346
|
})) {
|
|
26120
26347
|
const planDirective = buildPlanningDirective();
|
|
26121
26348
|
let typingActive2 = true;
|
|
26122
|
-
const
|
|
26349
|
+
const typingLoop = async () => {
|
|
26123
26350
|
while (typingActive2) {
|
|
26124
26351
|
try {
|
|
26125
26352
|
await channel.sendTyping?.(chatId);
|
|
@@ -26128,7 +26355,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26128
26355
|
await new Promise((r) => setTimeout(r, 4e3));
|
|
26129
26356
|
}
|
|
26130
26357
|
};
|
|
26131
|
-
|
|
26358
|
+
typingLoop().catch(() => {
|
|
26132
26359
|
});
|
|
26133
26360
|
try {
|
|
26134
26361
|
const planResponse = await askAgent(chatId, cleanText || text, {
|
|
@@ -26169,18 +26396,23 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26169
26396
|
}
|
|
26170
26397
|
return;
|
|
26171
26398
|
}
|
|
26172
|
-
|
|
26173
|
-
const
|
|
26174
|
-
|
|
26175
|
-
|
|
26176
|
-
|
|
26177
|
-
|
|
26399
|
+
const verboseForTyping = settings.getVerboseLevel();
|
|
26400
|
+
const showThinkingForTyping = settings.getShowThinkingUi();
|
|
26401
|
+
const needsLiveStatusForTyping = verboseForTyping !== "off" || showThinkingForTyping;
|
|
26402
|
+
let typingActive = !needsLiveStatusForTyping;
|
|
26403
|
+
if (typingActive) {
|
|
26404
|
+
const typingLoop = async () => {
|
|
26405
|
+
while (typingActive) {
|
|
26406
|
+
try {
|
|
26407
|
+
await channel.sendTyping?.(chatId);
|
|
26408
|
+
} catch {
|
|
26409
|
+
}
|
|
26410
|
+
await new Promise((r) => setTimeout(r, 4e3));
|
|
26178
26411
|
}
|
|
26179
|
-
|
|
26180
|
-
|
|
26181
|
-
|
|
26182
|
-
|
|
26183
|
-
});
|
|
26412
|
+
};
|
|
26413
|
+
typingLoop().catch(() => {
|
|
26414
|
+
});
|
|
26415
|
+
}
|
|
26184
26416
|
try {
|
|
26185
26417
|
const tMode = settings.getMode();
|
|
26186
26418
|
const tVerbose = settings.getVerboseLevel();
|
|
@@ -26803,6 +27035,13 @@ async function runWithRetry(job, model2, runId, t0) {
|
|
|
26803
27035
|
const cronBackend = currentBackend;
|
|
26804
27036
|
setAllowPaidSlots(chatId, cronBackend);
|
|
26805
27037
|
}
|
|
27038
|
+
if (job.credentialSlotId) {
|
|
27039
|
+
if (currentBackend === BACKEND.GEMINI) {
|
|
27040
|
+
pinChatGeminiSlot(chatId, job.credentialSlotId);
|
|
27041
|
+
} else {
|
|
27042
|
+
pinChatBackendSlot(chatId, currentBackend, job.credentialSlotId);
|
|
27043
|
+
}
|
|
27044
|
+
}
|
|
26806
27045
|
const response = await askAgent(chatId, job.description, {
|
|
26807
27046
|
model: currentModel,
|
|
26808
27047
|
backend: currentBackend,
|
|
@@ -26874,6 +27113,7 @@ var init_cron = __esm({
|
|
|
26874
27113
|
"src/scheduler/cron.ts"() {
|
|
26875
27114
|
"use strict";
|
|
26876
27115
|
init_store5();
|
|
27116
|
+
init_types();
|
|
26877
27117
|
init_backends();
|
|
26878
27118
|
init_agent();
|
|
26879
27119
|
init_log();
|
|
@@ -27564,6 +27804,115 @@ ${body.replace(/<[^>]*>/g, "").trim()}</code>
|
|
|
27564
27804
|
}
|
|
27565
27805
|
});
|
|
27566
27806
|
|
|
27807
|
+
// src/channels/health.ts
|
|
27808
|
+
function trackChannel(channel) {
|
|
27809
|
+
healthState.set(channel.name, {
|
|
27810
|
+
status: "healthy",
|
|
27811
|
+
startedAt: Date.now(),
|
|
27812
|
+
lastHealthyAt: Date.now(),
|
|
27813
|
+
lastErrorAt: null,
|
|
27814
|
+
lastError: null,
|
|
27815
|
+
reconnectAttempts: 0
|
|
27816
|
+
});
|
|
27817
|
+
}
|
|
27818
|
+
function markChannelHealthy(name) {
|
|
27819
|
+
const state = healthState.get(name);
|
|
27820
|
+
if (state) {
|
|
27821
|
+
state.status = "healthy";
|
|
27822
|
+
state.lastHealthyAt = Date.now();
|
|
27823
|
+
state.reconnectAttempts = 0;
|
|
27824
|
+
}
|
|
27825
|
+
}
|
|
27826
|
+
function markChannelDown(name, error3) {
|
|
27827
|
+
const state = healthState.get(name);
|
|
27828
|
+
if (state) {
|
|
27829
|
+
state.status = "down";
|
|
27830
|
+
state.lastErrorAt = Date.now();
|
|
27831
|
+
state.lastError = error3;
|
|
27832
|
+
warn(`[channel-health] ${name} marked as down: ${error3}`);
|
|
27833
|
+
}
|
|
27834
|
+
}
|
|
27835
|
+
function getChannelHealth(name) {
|
|
27836
|
+
const state = healthState.get(name);
|
|
27837
|
+
if (!state) return null;
|
|
27838
|
+
return {
|
|
27839
|
+
name,
|
|
27840
|
+
status: state.status,
|
|
27841
|
+
lastHealthyAt: state.lastHealthyAt,
|
|
27842
|
+
lastErrorAt: state.lastErrorAt,
|
|
27843
|
+
lastError: state.lastError,
|
|
27844
|
+
reconnectAttempts: state.reconnectAttempts,
|
|
27845
|
+
uptimeMs: state.status === "healthy" ? Date.now() - state.startedAt : 0
|
|
27846
|
+
};
|
|
27847
|
+
}
|
|
27848
|
+
async function attemptReconnect(channel, handler) {
|
|
27849
|
+
const state = healthState.get(channel.name);
|
|
27850
|
+
if (!state) return false;
|
|
27851
|
+
if (reconnecting.has(channel.name)) {
|
|
27852
|
+
log(`[channel-health] ${channel.name}: reconnect already in progress, skipping`);
|
|
27853
|
+
return false;
|
|
27854
|
+
}
|
|
27855
|
+
if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
27856
|
+
error(`[channel-health] ${channel.name}: max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached`);
|
|
27857
|
+
return false;
|
|
27858
|
+
}
|
|
27859
|
+
reconnecting.add(channel.name);
|
|
27860
|
+
state.reconnectAttempts++;
|
|
27861
|
+
const backoffMs = RECONNECT_BASE_MS * Math.pow(2, state.reconnectAttempts - 1);
|
|
27862
|
+
log(`[channel-health] ${channel.name}: reconnect attempt ${state.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS} in ${backoffMs}ms`);
|
|
27863
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
27864
|
+
try {
|
|
27865
|
+
await channel.stop().catch(() => {
|
|
27866
|
+
});
|
|
27867
|
+
await channel.start(handler);
|
|
27868
|
+
markChannelHealthy(channel.name);
|
|
27869
|
+
log(`[channel-health] ${channel.name}: reconnected successfully`);
|
|
27870
|
+
return true;
|
|
27871
|
+
} catch (err) {
|
|
27872
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
27873
|
+
markChannelDown(channel.name, msg);
|
|
27874
|
+
return false;
|
|
27875
|
+
} finally {
|
|
27876
|
+
reconnecting.delete(channel.name);
|
|
27877
|
+
}
|
|
27878
|
+
}
|
|
27879
|
+
function startHealthMonitor3(channels, handler) {
|
|
27880
|
+
registeredHandler = handler;
|
|
27881
|
+
for (const ch of channels) {
|
|
27882
|
+
trackChannel(ch);
|
|
27883
|
+
}
|
|
27884
|
+
healthInterval = setInterval(() => {
|
|
27885
|
+
for (const ch of channels) {
|
|
27886
|
+
const health = getChannelHealth(ch.name);
|
|
27887
|
+
if (health?.status === "down" && registeredHandler) {
|
|
27888
|
+
attemptReconnect(ch, registeredHandler).catch(() => {
|
|
27889
|
+
});
|
|
27890
|
+
}
|
|
27891
|
+
}
|
|
27892
|
+
}, HEALTH_CHECK_INTERVAL_MS);
|
|
27893
|
+
log(`[channel-health] Monitoring ${channels.length} channel(s)`);
|
|
27894
|
+
}
|
|
27895
|
+
function stopHealthMonitor3() {
|
|
27896
|
+
if (healthInterval) {
|
|
27897
|
+
clearInterval(healthInterval);
|
|
27898
|
+
healthInterval = null;
|
|
27899
|
+
}
|
|
27900
|
+
}
|
|
27901
|
+
var healthState, MAX_RECONNECT_ATTEMPTS, RECONNECT_BASE_MS, HEALTH_CHECK_INTERVAL_MS, healthInterval, registeredHandler, reconnecting;
|
|
27902
|
+
var init_health3 = __esm({
|
|
27903
|
+
"src/channels/health.ts"() {
|
|
27904
|
+
"use strict";
|
|
27905
|
+
init_log();
|
|
27906
|
+
healthState = /* @__PURE__ */ new Map();
|
|
27907
|
+
MAX_RECONNECT_ATTEMPTS = 5;
|
|
27908
|
+
RECONNECT_BASE_MS = 5e3;
|
|
27909
|
+
HEALTH_CHECK_INTERVAL_MS = 15e3;
|
|
27910
|
+
healthInterval = null;
|
|
27911
|
+
registeredHandler = null;
|
|
27912
|
+
reconnecting = /* @__PURE__ */ new Set();
|
|
27913
|
+
}
|
|
27914
|
+
});
|
|
27915
|
+
|
|
27567
27916
|
// src/channels/telegram.ts
|
|
27568
27917
|
import { API_CONSTANTS, Bot, GrammyError as GrammyError2, InlineKeyboard, InputFile } from "grammy";
|
|
27569
27918
|
function isFastPathMessage(msg) {
|
|
@@ -27594,10 +27943,11 @@ var init_telegram2 = __esm({
|
|
|
27594
27943
|
"use strict";
|
|
27595
27944
|
init_telegram();
|
|
27596
27945
|
init_log();
|
|
27946
|
+
init_health3();
|
|
27597
27947
|
init_store5();
|
|
27598
27948
|
init_telegram_throttle();
|
|
27599
27949
|
FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
|
|
27600
|
-
TelegramChannel = class {
|
|
27950
|
+
TelegramChannel = class _TelegramChannel {
|
|
27601
27951
|
name = "telegram";
|
|
27602
27952
|
bot;
|
|
27603
27953
|
allowedChatIds;
|
|
@@ -27607,6 +27957,19 @@ var init_telegram2 = __esm({
|
|
|
27607
27957
|
// messageId → chatId
|
|
27608
27958
|
reactionHandlers = [];
|
|
27609
27959
|
throttle;
|
|
27960
|
+
// ── Polling health tracking ─────────────────────────────────────────
|
|
27961
|
+
/** Timestamp of last update received from Telegram (message, callback, reaction) */
|
|
27962
|
+
lastUpdateAt = 0;
|
|
27963
|
+
/** True while polling is expected to be active (between start() and stop()) */
|
|
27964
|
+
pollingExpected = false;
|
|
27965
|
+
/** Watchdog interval that detects silent polling death */
|
|
27966
|
+
pollingWatchdog = null;
|
|
27967
|
+
/** Max time without any update before we consider polling dead (ms) */
|
|
27968
|
+
static POLLING_SILENCE_THRESHOLD_MS = 2 * 60 * 1e3;
|
|
27969
|
+
// 2 minutes
|
|
27970
|
+
/** How often the watchdog checks for polling health (ms) */
|
|
27971
|
+
static POLLING_WATCHDOG_INTERVAL_MS = 60 * 1e3;
|
|
27972
|
+
// 60 seconds
|
|
27610
27973
|
constructor() {
|
|
27611
27974
|
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
27612
27975
|
if (!token) {
|
|
@@ -27624,6 +27987,10 @@ var init_telegram2 = __esm({
|
|
|
27624
27987
|
this.bot = new Bot(token);
|
|
27625
27988
|
this.throttle = new TelegramThrottle();
|
|
27626
27989
|
this.throttle.setResumeNotifier(async (chatId, pausedSec, queuedCount) => {
|
|
27990
|
+
if (pausedSec > 60) {
|
|
27991
|
+
log(`[telegram] Skipping resume notification (paused ${pausedSec}s \u2014 too long, would risk another 429)`);
|
|
27992
|
+
return;
|
|
27993
|
+
}
|
|
27627
27994
|
try {
|
|
27628
27995
|
await this.bot.api.sendMessage(
|
|
27629
27996
|
numericChatId(chatId),
|
|
@@ -27735,6 +28102,7 @@ var init_telegram2 = __esm({
|
|
|
27735
28102
|
{ command: "council", description: "Multi-model debate (select models, anonymous rounds)" }
|
|
27736
28103
|
]);
|
|
27737
28104
|
this.bot.on("message", async (ctx) => {
|
|
28105
|
+
this.lastUpdateAt = Date.now();
|
|
27738
28106
|
const chatId = ctx.chat.id.toString();
|
|
27739
28107
|
const senderId = ctx.from?.id?.toString() ?? "";
|
|
27740
28108
|
const authorized = this.isAuthorized(chatId) || this.isAuthorized(senderId);
|
|
@@ -27762,6 +28130,7 @@ var init_telegram2 = __esm({
|
|
|
27762
28130
|
});
|
|
27763
28131
|
});
|
|
27764
28132
|
this.bot.on("callback_query:data", (ctx) => {
|
|
28133
|
+
this.lastUpdateAt = Date.now();
|
|
27765
28134
|
const userId = ctx.from.id.toString();
|
|
27766
28135
|
const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
|
|
27767
28136
|
log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
|
|
@@ -27789,6 +28158,7 @@ var init_telegram2 = __esm({
|
|
|
27789
28158
|
});
|
|
27790
28159
|
});
|
|
27791
28160
|
this.bot.on("message_reaction", async (ctx) => {
|
|
28161
|
+
this.lastUpdateAt = Date.now();
|
|
27792
28162
|
const chatId = String(ctx.chat.id);
|
|
27793
28163
|
const messageId = ctx.messageReaction.message_id;
|
|
27794
28164
|
if (!this.agentMessageIds.has(messageId)) return;
|
|
@@ -27805,6 +28175,7 @@ var init_telegram2 = __esm({
|
|
|
27805
28175
|
}
|
|
27806
28176
|
});
|
|
27807
28177
|
this.bot.on("inline_query", (ctx) => {
|
|
28178
|
+
this.lastUpdateAt = Date.now();
|
|
27808
28179
|
if (!this.isAuthorized(ctx.from.id.toString())) return;
|
|
27809
28180
|
this.handleInlineQuery(ctx).catch((err) => {
|
|
27810
28181
|
error("[telegram] Inline query error:", err);
|
|
@@ -27818,25 +28189,64 @@ var init_telegram2 = __esm({
|
|
|
27818
28189
|
error("[telegram] Unhandled error:", err);
|
|
27819
28190
|
}
|
|
27820
28191
|
});
|
|
27821
|
-
this.
|
|
28192
|
+
this.pollingExpected = true;
|
|
28193
|
+
this.lastUpdateAt = Date.now();
|
|
28194
|
+
const pollingPromise = this.bot.start({
|
|
27822
28195
|
allowed_updates: [...API_CONSTANTS.ALL_UPDATE_TYPES],
|
|
27823
28196
|
onStart: () => log("[telegram] Polling for messages...")
|
|
27824
|
-
}).catch((err) => {
|
|
27825
|
-
error("[telegram] Fatal: bot.start() failed:", err);
|
|
27826
|
-
error("[telegram] Check TELEGRAM_BOT_TOKEN in ~/.cc-claw/.env \u2014 it may be invalid or revoked.");
|
|
27827
|
-
error("[telegram] To regenerate: message @BotFather on Telegram, use /revoke, then 'cc-claw setup'");
|
|
27828
|
-
process.exit(1);
|
|
27829
28197
|
});
|
|
28198
|
+
pollingPromise.then(
|
|
28199
|
+
() => {
|
|
28200
|
+
if (this.pollingExpected) {
|
|
28201
|
+
error("[telegram] CRITICAL: Polling loop exited unexpectedly (resolved without stop)");
|
|
28202
|
+
markChannelDown("telegram", "Polling loop exited unexpectedly");
|
|
28203
|
+
}
|
|
28204
|
+
},
|
|
28205
|
+
(err) => {
|
|
28206
|
+
if (this.pollingExpected) {
|
|
28207
|
+
error("[telegram] Fatal: bot.start() failed:", err);
|
|
28208
|
+
error("[telegram] Check TELEGRAM_BOT_TOKEN in ~/.cc-claw/.env \u2014 it may be invalid or revoked.");
|
|
28209
|
+
error("[telegram] To regenerate: message @BotFather on Telegram, use /revoke, then 'cc-claw setup'");
|
|
28210
|
+
markChannelDown("telegram", `Polling error: ${err instanceof Error ? err.message : String(err)}`);
|
|
28211
|
+
}
|
|
28212
|
+
}
|
|
28213
|
+
);
|
|
28214
|
+
this.pollingWatchdog = setInterval(() => {
|
|
28215
|
+
if (!this.pollingExpected) return;
|
|
28216
|
+
const silenceMs = Date.now() - this.lastUpdateAt;
|
|
28217
|
+
if (silenceMs > _TelegramChannel.POLLING_SILENCE_THRESHOLD_MS) {
|
|
28218
|
+
error(
|
|
28219
|
+
`[telegram] CRITICAL: No updates received for ${Math.round(silenceMs / 1e3)}s \u2014 polling likely dead, triggering reconnect`
|
|
28220
|
+
);
|
|
28221
|
+
markChannelDown("telegram", `No updates for ${Math.round(silenceMs / 1e3)}s`);
|
|
28222
|
+
this.lastUpdateAt = Date.now();
|
|
28223
|
+
}
|
|
28224
|
+
}, _TelegramChannel.POLLING_WATCHDOG_INTERVAL_MS);
|
|
27830
28225
|
}
|
|
27831
28226
|
async stop() {
|
|
27832
|
-
|
|
28227
|
+
this.pollingExpected = false;
|
|
28228
|
+
if (this.pollingWatchdog) {
|
|
28229
|
+
clearInterval(this.pollingWatchdog);
|
|
28230
|
+
this.pollingWatchdog = null;
|
|
28231
|
+
}
|
|
28232
|
+
try {
|
|
28233
|
+
await this.bot.stop();
|
|
28234
|
+
} catch {
|
|
28235
|
+
}
|
|
28236
|
+
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
28237
|
+
if (token) {
|
|
28238
|
+
this.bot = new Bot(token);
|
|
28239
|
+
}
|
|
27833
28240
|
}
|
|
27834
28241
|
async sendTyping(chatId, threadId) {
|
|
27835
|
-
if (this.throttle.isPaused()) return;
|
|
27836
28242
|
try {
|
|
27837
|
-
await this.
|
|
27838
|
-
|
|
27839
|
-
|
|
28243
|
+
await this.throttle.tryBestEffort(
|
|
28244
|
+
chatId,
|
|
28245
|
+
"typing",
|
|
28246
|
+
() => this.bot.api.sendChatAction(numericChatId(chatId), "typing", {
|
|
28247
|
+
...threadId ? { message_thread_id: threadId } : {}
|
|
28248
|
+
})
|
|
28249
|
+
);
|
|
27840
28250
|
} catch {
|
|
27841
28251
|
}
|
|
27842
28252
|
}
|
|
@@ -27941,7 +28351,12 @@ var init_telegram2 = __esm({
|
|
|
27941
28351
|
);
|
|
27942
28352
|
return true;
|
|
27943
28353
|
} catch (err) {
|
|
27944
|
-
|
|
28354
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
28355
|
+
if (errMsg.includes("overflow") || errMsg.includes("rate limit") || errMsg.includes("max wait")) {
|
|
28356
|
+
warn("[telegram] editText skipped fallback (throttle overload):", errMsg);
|
|
28357
|
+
return false;
|
|
28358
|
+
}
|
|
28359
|
+
warn("[telegram] editText HTML failed, trying plain fallback:", errMsg);
|
|
27945
28360
|
try {
|
|
27946
28361
|
await this.throttle.send(
|
|
27947
28362
|
chatId,
|
|
@@ -28079,9 +28494,13 @@ var init_telegram2 = __esm({
|
|
|
28079
28494
|
}
|
|
28080
28495
|
async reactToMessage(chatId, messageId, emoji) {
|
|
28081
28496
|
try {
|
|
28082
|
-
await this.
|
|
28083
|
-
|
|
28084
|
-
|
|
28497
|
+
await this.throttle.tryBestEffort(
|
|
28498
|
+
chatId,
|
|
28499
|
+
"reaction",
|
|
28500
|
+
() => this.bot.api.setMessageReaction(numericChatId(chatId), parseInt(messageId), [
|
|
28501
|
+
{ type: "emoji", emoji }
|
|
28502
|
+
])
|
|
28503
|
+
);
|
|
28085
28504
|
} catch (err) {
|
|
28086
28505
|
log(`[telegram] reactToMessage failed (chat=${chatId} msg=${messageId}): ${err}`);
|
|
28087
28506
|
}
|
|
@@ -28499,115 +28918,6 @@ var init_bootstrap2 = __esm({
|
|
|
28499
28918
|
}
|
|
28500
28919
|
});
|
|
28501
28920
|
|
|
28502
|
-
// src/channels/health.ts
|
|
28503
|
-
function trackChannel(channel) {
|
|
28504
|
-
healthState.set(channel.name, {
|
|
28505
|
-
status: "healthy",
|
|
28506
|
-
startedAt: Date.now(),
|
|
28507
|
-
lastHealthyAt: Date.now(),
|
|
28508
|
-
lastErrorAt: null,
|
|
28509
|
-
lastError: null,
|
|
28510
|
-
reconnectAttempts: 0
|
|
28511
|
-
});
|
|
28512
|
-
}
|
|
28513
|
-
function markChannelHealthy(name) {
|
|
28514
|
-
const state = healthState.get(name);
|
|
28515
|
-
if (state) {
|
|
28516
|
-
state.status = "healthy";
|
|
28517
|
-
state.lastHealthyAt = Date.now();
|
|
28518
|
-
state.reconnectAttempts = 0;
|
|
28519
|
-
}
|
|
28520
|
-
}
|
|
28521
|
-
function markChannelDown(name, error3) {
|
|
28522
|
-
const state = healthState.get(name);
|
|
28523
|
-
if (state) {
|
|
28524
|
-
state.status = "down";
|
|
28525
|
-
state.lastErrorAt = Date.now();
|
|
28526
|
-
state.lastError = error3;
|
|
28527
|
-
warn(`[channel-health] ${name} marked as down: ${error3}`);
|
|
28528
|
-
}
|
|
28529
|
-
}
|
|
28530
|
-
function getChannelHealth(name) {
|
|
28531
|
-
const state = healthState.get(name);
|
|
28532
|
-
if (!state) return null;
|
|
28533
|
-
return {
|
|
28534
|
-
name,
|
|
28535
|
-
status: state.status,
|
|
28536
|
-
lastHealthyAt: state.lastHealthyAt,
|
|
28537
|
-
lastErrorAt: state.lastErrorAt,
|
|
28538
|
-
lastError: state.lastError,
|
|
28539
|
-
reconnectAttempts: state.reconnectAttempts,
|
|
28540
|
-
uptimeMs: state.status === "healthy" ? Date.now() - state.startedAt : 0
|
|
28541
|
-
};
|
|
28542
|
-
}
|
|
28543
|
-
async function attemptReconnect(channel, handler) {
|
|
28544
|
-
const state = healthState.get(channel.name);
|
|
28545
|
-
if (!state) return false;
|
|
28546
|
-
if (reconnecting.has(channel.name)) {
|
|
28547
|
-
log(`[channel-health] ${channel.name}: reconnect already in progress, skipping`);
|
|
28548
|
-
return false;
|
|
28549
|
-
}
|
|
28550
|
-
if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
28551
|
-
error(`[channel-health] ${channel.name}: max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached`);
|
|
28552
|
-
return false;
|
|
28553
|
-
}
|
|
28554
|
-
reconnecting.add(channel.name);
|
|
28555
|
-
state.reconnectAttempts++;
|
|
28556
|
-
const backoffMs = RECONNECT_BASE_MS * Math.pow(2, state.reconnectAttempts - 1);
|
|
28557
|
-
log(`[channel-health] ${channel.name}: reconnect attempt ${state.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS} in ${backoffMs}ms`);
|
|
28558
|
-
await new Promise((r) => setTimeout(r, backoffMs));
|
|
28559
|
-
try {
|
|
28560
|
-
await channel.stop().catch(() => {
|
|
28561
|
-
});
|
|
28562
|
-
await channel.start(handler);
|
|
28563
|
-
markChannelHealthy(channel.name);
|
|
28564
|
-
log(`[channel-health] ${channel.name}: reconnected successfully`);
|
|
28565
|
-
return true;
|
|
28566
|
-
} catch (err) {
|
|
28567
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
28568
|
-
markChannelDown(channel.name, msg);
|
|
28569
|
-
return false;
|
|
28570
|
-
} finally {
|
|
28571
|
-
reconnecting.delete(channel.name);
|
|
28572
|
-
}
|
|
28573
|
-
}
|
|
28574
|
-
function startHealthMonitor3(channels, handler) {
|
|
28575
|
-
registeredHandler = handler;
|
|
28576
|
-
for (const ch of channels) {
|
|
28577
|
-
trackChannel(ch);
|
|
28578
|
-
}
|
|
28579
|
-
healthInterval = setInterval(() => {
|
|
28580
|
-
for (const ch of channels) {
|
|
28581
|
-
const health = getChannelHealth(ch.name);
|
|
28582
|
-
if (health?.status === "down" && registeredHandler) {
|
|
28583
|
-
attemptReconnect(ch, registeredHandler).catch(() => {
|
|
28584
|
-
});
|
|
28585
|
-
}
|
|
28586
|
-
}
|
|
28587
|
-
}, HEALTH_CHECK_INTERVAL_MS);
|
|
28588
|
-
log(`[channel-health] Monitoring ${channels.length} channel(s)`);
|
|
28589
|
-
}
|
|
28590
|
-
function stopHealthMonitor3() {
|
|
28591
|
-
if (healthInterval) {
|
|
28592
|
-
clearInterval(healthInterval);
|
|
28593
|
-
healthInterval = null;
|
|
28594
|
-
}
|
|
28595
|
-
}
|
|
28596
|
-
var healthState, MAX_RECONNECT_ATTEMPTS, RECONNECT_BASE_MS, HEALTH_CHECK_INTERVAL_MS, healthInterval, registeredHandler, reconnecting;
|
|
28597
|
-
var init_health3 = __esm({
|
|
28598
|
-
"src/channels/health.ts"() {
|
|
28599
|
-
"use strict";
|
|
28600
|
-
init_log();
|
|
28601
|
-
healthState = /* @__PURE__ */ new Map();
|
|
28602
|
-
MAX_RECONNECT_ATTEMPTS = 5;
|
|
28603
|
-
RECONNECT_BASE_MS = 5e3;
|
|
28604
|
-
HEALTH_CHECK_INTERVAL_MS = 15e3;
|
|
28605
|
-
healthInterval = null;
|
|
28606
|
-
registeredHandler = null;
|
|
28607
|
-
reconnecting = /* @__PURE__ */ new Set();
|
|
28608
|
-
}
|
|
28609
|
-
});
|
|
28610
|
-
|
|
28611
28921
|
// src/cli/commands/ai-skill.ts
|
|
28612
28922
|
var ai_skill_exports = {};
|
|
28613
28923
|
__export(ai_skill_exports, {
|
package/package.json
CHANGED