cc-claw 0.20.14 → 0.20.16
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 +649 -201
- 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.16" : (() => {
|
|
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,29 @@ var init_store5 = __esm({
|
|
|
4141
4149
|
}
|
|
4142
4150
|
});
|
|
4143
4151
|
|
|
4152
|
+
// src/backends/types.ts
|
|
4153
|
+
var types_exports = {};
|
|
4154
|
+
__export(types_exports, {
|
|
4155
|
+
BACKEND: () => BACKEND,
|
|
4156
|
+
isBackendId: () => isBackendId
|
|
4157
|
+
});
|
|
4158
|
+
function isBackendId(value) {
|
|
4159
|
+
return Object.values(BACKEND).includes(value);
|
|
4160
|
+
}
|
|
4161
|
+
var BACKEND;
|
|
4162
|
+
var init_types = __esm({
|
|
4163
|
+
"src/backends/types.ts"() {
|
|
4164
|
+
"use strict";
|
|
4165
|
+
BACKEND = {
|
|
4166
|
+
CLAUDE: "claude",
|
|
4167
|
+
GEMINI: "gemini",
|
|
4168
|
+
CODEX: "codex",
|
|
4169
|
+
CURSOR: "cursor",
|
|
4170
|
+
OLLAMA: "ollama"
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
});
|
|
4174
|
+
|
|
4144
4175
|
// src/env.ts
|
|
4145
4176
|
import { homedir as homedir2 } from "os";
|
|
4146
4177
|
function stripProxyVars(env) {
|
|
@@ -4223,6 +4254,11 @@ var init_strip_thinking = __esm({
|
|
|
4223
4254
|
});
|
|
4224
4255
|
|
|
4225
4256
|
// src/backends/resolve-executable.ts
|
|
4257
|
+
var resolve_executable_exports = {};
|
|
4258
|
+
__export(resolve_executable_exports, {
|
|
4259
|
+
clearExecutableCache: () => clearExecutableCache,
|
|
4260
|
+
resolveExecutable: () => resolveExecutable
|
|
4261
|
+
});
|
|
4226
4262
|
import { existsSync as existsSync2 } from "fs";
|
|
4227
4263
|
import { spawnSync } from "child_process";
|
|
4228
4264
|
function resolveExecutable(config2) {
|
|
@@ -4259,6 +4295,9 @@ function resolveExecutable(config2) {
|
|
|
4259
4295
|
cache.set(config2.binaryName, fallback);
|
|
4260
4296
|
return fallback;
|
|
4261
4297
|
}
|
|
4298
|
+
function clearExecutableCache() {
|
|
4299
|
+
cache.clear();
|
|
4300
|
+
}
|
|
4262
4301
|
var cache;
|
|
4263
4302
|
var init_resolve_executable = __esm({
|
|
4264
4303
|
"src/backends/resolve-executable.ts"() {
|
|
@@ -6494,24 +6533,6 @@ var init_ollama2 = __esm({
|
|
|
6494
6533
|
}
|
|
6495
6534
|
});
|
|
6496
6535
|
|
|
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
6536
|
// src/backends/index.ts
|
|
6516
6537
|
var backends_exports = {};
|
|
6517
6538
|
__export(backends_exports, {
|
|
@@ -14978,8 +14999,8 @@ var init_telegram_throttle = __esm({
|
|
|
14978
14999
|
"src/channels/telegram-throttle.ts"() {
|
|
14979
15000
|
"use strict";
|
|
14980
15001
|
init_log();
|
|
14981
|
-
PER_CHAT_INTERVAL_MS =
|
|
14982
|
-
GLOBAL_INTERVAL_MS =
|
|
15002
|
+
PER_CHAT_INTERVAL_MS = 1e3;
|
|
15003
|
+
GLOBAL_INTERVAL_MS = 100;
|
|
14983
15004
|
MAX_RETRIES2 = 2;
|
|
14984
15005
|
RETRY_DELAY_MS = 1e3;
|
|
14985
15006
|
MAX_QUEUE_SIZE = 100;
|
|
@@ -15007,6 +15028,9 @@ var init_telegram_throttle = __esm({
|
|
|
15007
15028
|
}
|
|
15008
15029
|
/** Enqueue a Telegram API call with automatic pacing and 429 handling. */
|
|
15009
15030
|
async send(chatId, label2, fn) {
|
|
15031
|
+
if (this.isPaused() && label2.startsWith("editText")) {
|
|
15032
|
+
throw new Error("Throttle paused (rate limit active) \u2014 edit skipped");
|
|
15033
|
+
}
|
|
15010
15034
|
return new Promise((resolve, reject) => {
|
|
15011
15035
|
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
15012
15036
|
const dropped = this.queue.shift();
|
|
@@ -15019,6 +15043,31 @@ var init_telegram_throttle = __esm({
|
|
|
15019
15043
|
this.drain();
|
|
15020
15044
|
});
|
|
15021
15045
|
}
|
|
15046
|
+
/**
|
|
15047
|
+
* Best-effort send — drops silently if throttle is paused or queue is pressured.
|
|
15048
|
+
* Used for cosmetic calls (typing indicators, reactions) that should count toward
|
|
15049
|
+
* rate limits but must never queue up or amplify 429 spirals.
|
|
15050
|
+
*/
|
|
15051
|
+
async tryBestEffort(chatId, label2, fn) {
|
|
15052
|
+
if (this.isPaused()) return void 0;
|
|
15053
|
+
if (this.queue.length > 10) return void 0;
|
|
15054
|
+
const lastChat = this.lastSendPerChat.get(chatId) ?? 0;
|
|
15055
|
+
if (Date.now() - lastChat < PER_CHAT_INTERVAL_MS) return void 0;
|
|
15056
|
+
if (Date.now() - this.lastGlobalSend < GLOBAL_INTERVAL_MS) return void 0;
|
|
15057
|
+
try {
|
|
15058
|
+
const result = await fn();
|
|
15059
|
+
this.recordSend(chatId);
|
|
15060
|
+
return result;
|
|
15061
|
+
} catch (err) {
|
|
15062
|
+
if (is429(err)) {
|
|
15063
|
+
const retrySec = err.parameters?.retry_after ?? 10;
|
|
15064
|
+
this.pausedUntil = Date.now() + retrySec * 1e3;
|
|
15065
|
+
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
15066
|
+
warn(`[throttle] Best-effort ${label2} hit 429, pausing for ${retrySec}s`);
|
|
15067
|
+
}
|
|
15068
|
+
return void 0;
|
|
15069
|
+
}
|
|
15070
|
+
}
|
|
15022
15071
|
/** Check whether the throttle is currently paused (rate-limited). */
|
|
15023
15072
|
isPaused() {
|
|
15024
15073
|
return Date.now() < this.pausedUntil;
|
|
@@ -15101,12 +15150,13 @@ var init_telegram_throttle = __esm({
|
|
|
15101
15150
|
// ── Pause management ────────────────────────────────────────────────
|
|
15102
15151
|
enterPause(retrySec, failedItem) {
|
|
15103
15152
|
this.queue.unshift(failedItem);
|
|
15104
|
-
|
|
15153
|
+
const bufferedSec = Math.ceil(retrySec * 1.5);
|
|
15154
|
+
this.pausedUntil = Date.now() + bufferedSec * 1e3;
|
|
15105
15155
|
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
15106
15156
|
for (const qi of this.queue) {
|
|
15107
15157
|
this.chatsPendingNotification.add(qi.chatId);
|
|
15108
15158
|
}
|
|
15109
|
-
warn(`[throttle] 429 \u2014 pausing ALL sends for ${retrySec}s
|
|
15159
|
+
warn(`[throttle] 429 \u2014 pausing ALL sends for ${bufferedSec}s (retry_after=${retrySec}s + 50% buffer, ${this.queue.length} items queued)`);
|
|
15110
15160
|
}
|
|
15111
15161
|
async sendResumeNotifications() {
|
|
15112
15162
|
const chats2 = new Set(this.chatsPendingNotification);
|
|
@@ -15739,6 +15789,7 @@ var init_profile = __esm({
|
|
|
15739
15789
|
var classify_exports = {};
|
|
15740
15790
|
__export(classify_exports, {
|
|
15741
15791
|
classifyIntent: () => classifyIntent,
|
|
15792
|
+
classifyIntentAsync: () => classifyIntentAsync,
|
|
15742
15793
|
getIntentStats: () => getIntentStats,
|
|
15743
15794
|
resetIntentStats: () => resetIntentStats
|
|
15744
15795
|
});
|
|
@@ -15749,12 +15800,17 @@ function resetIntentStats() {
|
|
|
15749
15800
|
intentCounts.chat = 0;
|
|
15750
15801
|
intentCounts.agentic = 0;
|
|
15751
15802
|
}
|
|
15752
|
-
function
|
|
15803
|
+
function classifyIntentFast(text, chatId) {
|
|
15753
15804
|
const trimmed = text.trim();
|
|
15754
15805
|
if (trimmed.startsWith(">>")) return "agentic";
|
|
15755
15806
|
if (trimmed.startsWith("/")) return "agentic";
|
|
15756
15807
|
const lower = trimmed.toLowerCase();
|
|
15757
15808
|
const normalized = trimmed.replace(/^["'\u201C\u201D\u2018\u2019`\s]+|["'\u201C\u201D\u2018\u2019`\s]+$/g, "");
|
|
15809
|
+
const lowerNoPunct = lower.replace(/[?!.,…]+$/, "").trim();
|
|
15810
|
+
if (CHAT_EXACT.has(lowerNoPunct) || CHAT_EXACT.has(lower)) {
|
|
15811
|
+
log(`[intent] "${lower}" -> chat (exact match)`);
|
|
15812
|
+
return "chat";
|
|
15813
|
+
}
|
|
15758
15814
|
const sessionId = getSessionId(chatId);
|
|
15759
15815
|
if (sessionId) {
|
|
15760
15816
|
const lastTs = getLastMessageTimestamp(chatId);
|
|
@@ -15762,47 +15818,148 @@ function classifyIntent(text, chatId) {
|
|
|
15762
15818
|
const elapsed = Date.now() - lastTs;
|
|
15763
15819
|
if (elapsed < 12e4 && trimmed.length < 30) {
|
|
15764
15820
|
log(`[intent] "${trimmed.slice(0, 30)}" -> agentic (active session, ${(elapsed / 1e3).toFixed(0)}s ago)`);
|
|
15765
|
-
intentCounts.agentic++;
|
|
15766
15821
|
return "agentic";
|
|
15767
15822
|
}
|
|
15768
15823
|
}
|
|
15769
15824
|
}
|
|
15770
|
-
if (CHAT_EXACT.has(lower)) {
|
|
15771
|
-
log(`[intent] "${lower}" -> chat (exact match)`);
|
|
15772
|
-
intentCounts.chat++;
|
|
15773
|
-
return "chat";
|
|
15774
|
-
}
|
|
15775
15825
|
if (trimmed.length <= 4 && /^[\p{Emoji}\s]+$/u.test(trimmed)) {
|
|
15776
15826
|
log(`[intent] "${trimmed}" -> chat (emoji-only)`);
|
|
15777
|
-
intentCounts.chat++;
|
|
15778
15827
|
return "chat";
|
|
15779
15828
|
}
|
|
15780
15829
|
for (const pattern of STRUCTURAL_PATTERNS) {
|
|
15781
15830
|
if (pattern.test(normalized)) {
|
|
15782
15831
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (structural: ${pattern})`);
|
|
15783
|
-
intentCounts.agentic++;
|
|
15784
15832
|
return "agentic";
|
|
15785
15833
|
}
|
|
15786
15834
|
}
|
|
15787
15835
|
for (const pattern of MUTATION_PATTERNS) {
|
|
15788
15836
|
if (pattern.test(normalized)) {
|
|
15789
15837
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (mutation: ${pattern})`);
|
|
15790
|
-
intentCounts.agentic++;
|
|
15791
15838
|
return "agentic";
|
|
15792
15839
|
}
|
|
15793
15840
|
}
|
|
15794
15841
|
for (const pattern of CHAT_QUESTION_PATTERNS) {
|
|
15795
15842
|
if (pattern.test(normalized)) {
|
|
15796
15843
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> chat (question: ${pattern})`);
|
|
15797
|
-
intentCounts.chat++;
|
|
15798
15844
|
return "chat";
|
|
15799
15845
|
}
|
|
15800
15846
|
}
|
|
15801
|
-
|
|
15847
|
+
return null;
|
|
15848
|
+
}
|
|
15849
|
+
async function classifyWithLlm(text) {
|
|
15850
|
+
try {
|
|
15851
|
+
const ollamaResult = await classifyWithOllama(text);
|
|
15852
|
+
if (ollamaResult) return ollamaResult;
|
|
15853
|
+
} catch {
|
|
15854
|
+
}
|
|
15855
|
+
try {
|
|
15856
|
+
const cliResult = await classifyWithSummarizerCli(text);
|
|
15857
|
+
if (cliResult) return cliResult;
|
|
15858
|
+
} catch {
|
|
15859
|
+
}
|
|
15860
|
+
return null;
|
|
15861
|
+
}
|
|
15862
|
+
async function classifyWithOllama(text) {
|
|
15863
|
+
const ollamaService = await Promise.resolve().then(() => (init_service(), service_exports));
|
|
15864
|
+
const ollamaClient = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
15865
|
+
const servers = ollamaService.listServers();
|
|
15866
|
+
const onlineServer = servers.find((s) => s.status === "online");
|
|
15867
|
+
if (!onlineServer) return null;
|
|
15868
|
+
const models = ollamaService.listModels(onlineServer.name);
|
|
15869
|
+
if (models.length === 0) return null;
|
|
15870
|
+
const sorted = [...models].sort(
|
|
15871
|
+
(a, b) => (a.sizeBytes ?? Infinity) - (b.sizeBytes ?? Infinity)
|
|
15872
|
+
);
|
|
15873
|
+
const model2 = sorted[0].name;
|
|
15874
|
+
const result = await ollamaClient.chat(
|
|
15875
|
+
onlineServer.baseUrl,
|
|
15876
|
+
model2,
|
|
15877
|
+
[{ role: "user", content: LLM_CLASSIFY_PROMPT + text.slice(0, 500) }],
|
|
15878
|
+
{ timeoutMs: LLM_CLASSIFY_TIMEOUT_MS, maxTokens: 5, temperature: 0 }
|
|
15879
|
+
);
|
|
15880
|
+
return parseClassifyResponse(result.text);
|
|
15881
|
+
}
|
|
15882
|
+
async function classifyWithSummarizerCli(text) {
|
|
15883
|
+
const { getSummarizer: getSummarizer3 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
|
|
15884
|
+
const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
15885
|
+
const config2 = getSummarizer3("__global__");
|
|
15886
|
+
let backendId = config2.backend;
|
|
15887
|
+
let modelName = config2.model;
|
|
15888
|
+
if (!backendId) {
|
|
15889
|
+
const adapters2 = getAllAdapters5();
|
|
15890
|
+
const cheapAdapter = adapters2.find((a) => a.summarizerModel);
|
|
15891
|
+
if (!cheapAdapter) return null;
|
|
15892
|
+
backendId = cheapAdapter.id;
|
|
15893
|
+
modelName = cheapAdapter.summarizerModel;
|
|
15894
|
+
}
|
|
15895
|
+
const adapter = getAdapter4(backendId);
|
|
15896
|
+
if (!adapter) return null;
|
|
15897
|
+
const model2 = modelName ?? adapter.summarizerModel;
|
|
15898
|
+
const { spawn: spawn8 } = await import("child_process");
|
|
15899
|
+
const { resolveExecutable: resolveExecutable4 } = await Promise.resolve().then(() => (init_resolve_executable(), resolve_executable_exports));
|
|
15900
|
+
const exe = resolveExecutable4(adapter.id);
|
|
15901
|
+
if (!exe) return null;
|
|
15902
|
+
return new Promise((resolve) => {
|
|
15903
|
+
const timeout = setTimeout(() => {
|
|
15904
|
+
proc.kill("SIGKILL");
|
|
15905
|
+
resolve(null);
|
|
15906
|
+
}, LLM_CLASSIFY_TIMEOUT_MS);
|
|
15907
|
+
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];
|
|
15908
|
+
const proc = spawn8(exe, args, {
|
|
15909
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
15910
|
+
timeout: LLM_CLASSIFY_TIMEOUT_MS + 1e3
|
|
15911
|
+
});
|
|
15912
|
+
let output2 = "";
|
|
15913
|
+
proc.stdout?.on("data", (chunk) => {
|
|
15914
|
+
output2 += chunk.toString();
|
|
15915
|
+
});
|
|
15916
|
+
proc.on("close", () => {
|
|
15917
|
+
clearTimeout(timeout);
|
|
15918
|
+
resolve(parseClassifyResponse(output2));
|
|
15919
|
+
});
|
|
15920
|
+
proc.on("error", () => {
|
|
15921
|
+
clearTimeout(timeout);
|
|
15922
|
+
resolve(null);
|
|
15923
|
+
});
|
|
15924
|
+
});
|
|
15925
|
+
}
|
|
15926
|
+
function parseClassifyResponse(text) {
|
|
15927
|
+
const lower = text.trim().toLowerCase();
|
|
15928
|
+
if (lower.includes("chat")) return "chat";
|
|
15929
|
+
if (lower.includes("task")) return "agentic";
|
|
15930
|
+
return null;
|
|
15931
|
+
}
|
|
15932
|
+
function classifyIntent(text, chatId) {
|
|
15933
|
+
const fast = classifyIntentFast(text, chatId);
|
|
15934
|
+
if (fast) {
|
|
15935
|
+
intentCounts[fast]++;
|
|
15936
|
+
return fast;
|
|
15937
|
+
}
|
|
15938
|
+
log(`[intent] "${text.slice(0, 40)}..." -> agentic (default)`);
|
|
15939
|
+
intentCounts.agentic++;
|
|
15940
|
+
return "agentic";
|
|
15941
|
+
}
|
|
15942
|
+
async function classifyIntentAsync(text, chatId) {
|
|
15943
|
+
const fast = classifyIntentFast(text, chatId);
|
|
15944
|
+
if (fast) {
|
|
15945
|
+
intentCounts[fast]++;
|
|
15946
|
+
return fast;
|
|
15947
|
+
}
|
|
15948
|
+
try {
|
|
15949
|
+
const llmResult = await classifyWithLlm(text);
|
|
15950
|
+
if (llmResult) {
|
|
15951
|
+
log(`[intent] "${text.slice(0, 40)}..." -> ${llmResult} (LLM)`);
|
|
15952
|
+
intentCounts[llmResult]++;
|
|
15953
|
+
return llmResult;
|
|
15954
|
+
}
|
|
15955
|
+
} catch (err) {
|
|
15956
|
+
warn(`[intent] LLM classification failed: ${err instanceof Error ? err.message : err}`);
|
|
15957
|
+
}
|
|
15958
|
+
log(`[intent] "${text.slice(0, 40)}..." -> agentic (default, LLM unavailable)`);
|
|
15802
15959
|
intentCounts.agentic++;
|
|
15803
15960
|
return "agentic";
|
|
15804
15961
|
}
|
|
15805
|
-
var intentCounts, CHAT_EXACT, MUTATION_PATTERNS, CHAT_QUESTION_PATTERNS, STRUCTURAL_PATTERNS;
|
|
15962
|
+
var intentCounts, CHAT_EXACT, MUTATION_PATTERNS, CHAT_QUESTION_PATTERNS, STRUCTURAL_PATTERNS, LLM_CLASSIFY_PROMPT, LLM_CLASSIFY_TIMEOUT_MS;
|
|
15806
15963
|
var init_classify = __esm({
|
|
15807
15964
|
"src/intent/classify.ts"() {
|
|
15808
15965
|
"use strict";
|
|
@@ -15898,6 +16055,13 @@ var init_classify = __esm({
|
|
|
15898
16055
|
/\b(error|bug|crash|fail|broken|issue|problem|exception|stack\s?trace)\b/i,
|
|
15899
16056
|
/\b(function|class|const|let|var|import|export|return|async|await)\b/i
|
|
15900
16057
|
];
|
|
16058
|
+
LLM_CLASSIFY_PROMPT = `Classify this message as either "chat" or "task".
|
|
16059
|
+
- "chat" = greeting, small talk, acknowledgment, question, opinion, or anything conversational
|
|
16060
|
+
- "task" = request to DO something (research, draft, analyze, code, create, fix, etc.)
|
|
16061
|
+
Reply with ONLY the word "chat" or "task", nothing else.
|
|
16062
|
+
|
|
16063
|
+
Message: `;
|
|
16064
|
+
LLM_CLASSIFY_TIMEOUT_MS = 3e3;
|
|
15901
16065
|
}
|
|
15902
16066
|
});
|
|
15903
16067
|
|
|
@@ -15928,6 +16092,17 @@ var init_types3 = __esm({
|
|
|
15928
16092
|
});
|
|
15929
16093
|
|
|
15930
16094
|
// src/scheduler/wizard.ts
|
|
16095
|
+
var wizard_exports = {};
|
|
16096
|
+
__export(wizard_exports, {
|
|
16097
|
+
cancelWizard: () => cancelWizard,
|
|
16098
|
+
getPendingJob: () => getPendingJob,
|
|
16099
|
+
handleWizardCallback: () => handleWizardCallback,
|
|
16100
|
+
handleWizardText: () => handleWizardText,
|
|
16101
|
+
hasPendingWizard: () => hasPendingWizard,
|
|
16102
|
+
resolveSlotLabel: () => resolveSlotLabel,
|
|
16103
|
+
startEditWizard: () => startEditWizard,
|
|
16104
|
+
startWizard: () => startWizard
|
|
16105
|
+
});
|
|
15931
16106
|
function hasPendingWizard(chatId) {
|
|
15932
16107
|
return pendingJobs.has(chatId);
|
|
15933
16108
|
}
|
|
@@ -15948,6 +16123,9 @@ function resetWizardTimeout(chatId) {
|
|
|
15948
16123
|
log(`[wizard] Auto-cancelled stale wizard for chat ${chatId}`);
|
|
15949
16124
|
}, WIZARD_TIMEOUT_MS));
|
|
15950
16125
|
}
|
|
16126
|
+
function getPendingJob(chatId) {
|
|
16127
|
+
return pendingJobs.get(chatId);
|
|
16128
|
+
}
|
|
15951
16129
|
function parseNaturalLanguage(input) {
|
|
15952
16130
|
const lower = input.toLowerCase();
|
|
15953
16131
|
const minMatch = lower.match(/every\s+(\d+)\s+min/);
|
|
@@ -16124,8 +16302,8 @@ async function handleWizardText(chatId, text, channel) {
|
|
|
16124
16302
|
case "thinking": {
|
|
16125
16303
|
const level = text.trim().toLowerCase();
|
|
16126
16304
|
pending.thinking = level || "auto";
|
|
16127
|
-
pending.step = "
|
|
16128
|
-
await
|
|
16305
|
+
pending.step = "account";
|
|
16306
|
+
await promptAccount(chatId, channel);
|
|
16129
16307
|
break;
|
|
16130
16308
|
}
|
|
16131
16309
|
case "timeout": {
|
|
@@ -16218,6 +16396,17 @@ async function handleWizardCallback(chatId, data, channel) {
|
|
|
16218
16396
|
await promptThinking(chatId, channel);
|
|
16219
16397
|
} else if (data.startsWith("sched:thinking:")) {
|
|
16220
16398
|
pending.thinking = data.slice(15);
|
|
16399
|
+
pending.step = "account";
|
|
16400
|
+
await promptAccount(chatId, channel);
|
|
16401
|
+
} else if (data.startsWith("sched:slot:")) {
|
|
16402
|
+
const val = data.slice(11);
|
|
16403
|
+
if (val === "auto") {
|
|
16404
|
+
pending.credentialSlotId = null;
|
|
16405
|
+
} else {
|
|
16406
|
+
pending.credentialSlotId = parseInt(val, 10);
|
|
16407
|
+
}
|
|
16408
|
+
const slotLabel = resolveSlotLabel(pending.backend, pending.credentialSlotId);
|
|
16409
|
+
await channel.sendText(chatId, `Account: ${slotLabel}`, { parseMode: "plain" });
|
|
16221
16410
|
pending.step = "timeout";
|
|
16222
16411
|
await promptTimeout(chatId, channel);
|
|
16223
16412
|
} else if (data.startsWith("sched:timeout:")) {
|
|
@@ -16323,9 +16512,52 @@ async function promptThinking(chatId, channel) {
|
|
|
16323
16512
|
await channel.sendKeyboard(chatId, "Thinking/effort level for this job?", buttons);
|
|
16324
16513
|
} else {
|
|
16325
16514
|
pending.thinking = "auto";
|
|
16515
|
+
pending.step = "account";
|
|
16516
|
+
await promptAccount(chatId, channel);
|
|
16517
|
+
}
|
|
16518
|
+
}
|
|
16519
|
+
async function promptAccount(chatId, channel) {
|
|
16520
|
+
const pending = pendingJobs.get(chatId);
|
|
16521
|
+
if (!pending?.backend) {
|
|
16522
|
+
if (pending) pending.step = "timeout";
|
|
16523
|
+
await promptTimeout(chatId, channel);
|
|
16524
|
+
return;
|
|
16525
|
+
}
|
|
16526
|
+
const isGemini = pending.backend === BACKEND.GEMINI;
|
|
16527
|
+
const slots = isGemini ? getGeminiSlots() : getBackendSlots(pending.backend);
|
|
16528
|
+
const enabledSlots = slots.filter((s) => s.enabled);
|
|
16529
|
+
if (enabledSlots.length === 0) {
|
|
16530
|
+
pending.credentialSlotId = null;
|
|
16326
16531
|
pending.step = "timeout";
|
|
16327
16532
|
await promptTimeout(chatId, channel);
|
|
16533
|
+
return;
|
|
16534
|
+
}
|
|
16535
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
16536
|
+
await channel.sendText(chatId, `Enter account slot ID, or "auto" for rotation:`, { parseMode: "plain" });
|
|
16537
|
+
return;
|
|
16328
16538
|
}
|
|
16539
|
+
const buttons = [
|
|
16540
|
+
[{ label: "\u{1F504} Auto (rotate)", data: "sched:slot:auto" }]
|
|
16541
|
+
];
|
|
16542
|
+
for (const slot of enabledSlots) {
|
|
16543
|
+
const s = slot;
|
|
16544
|
+
const icon = s.slotType === "api_key" ? "\u{1F511}" : "\u{1F4E7}";
|
|
16545
|
+
const label2 = s.label || s.email || `Slot #${s.id}`;
|
|
16546
|
+
const type = s.slotType === "api_key" ? "API key" : "OAuth";
|
|
16547
|
+
buttons.push([{ label: `${icon} ${label2} (${type})`, data: `sched:slot:${s.id}` }]);
|
|
16548
|
+
}
|
|
16549
|
+
await channel.sendKeyboard(chatId, "Which account should this job use?", buttons);
|
|
16550
|
+
}
|
|
16551
|
+
function resolveSlotLabel(backend2, slotId) {
|
|
16552
|
+
if (!slotId || !backend2) return "Auto (rotate)";
|
|
16553
|
+
const isGemini = backend2 === BACKEND.GEMINI;
|
|
16554
|
+
const slots = isGemini ? getGeminiSlots() : getBackendSlots(backend2);
|
|
16555
|
+
const slot = slots.find((s) => s.id === slotId);
|
|
16556
|
+
if (!slot) return `Slot #${slotId} (unknown)`;
|
|
16557
|
+
const icon = slot.slotType === "api_key" ? "\u{1F511}" : "\u{1F4E7}";
|
|
16558
|
+
const label2 = slot.label || slot.email || `Slot #${slot.id}`;
|
|
16559
|
+
const type = slot.slotType === "api_key" ? "API key" : "OAuth";
|
|
16560
|
+
return `${icon} ${label2} (${type})`;
|
|
16329
16561
|
}
|
|
16330
16562
|
async function promptTimeout(chatId, channel) {
|
|
16331
16563
|
if (typeof channel.sendKeyboard !== "function") {
|
|
@@ -16380,6 +16612,7 @@ async function promptConfirm(chatId, channel) {
|
|
|
16380
16612
|
if (!pending) return;
|
|
16381
16613
|
const backendName = pending.backend ? getAdapter(pending.backend).displayName : "default";
|
|
16382
16614
|
const timeoutLabel = pending.timeout ? `${pending.timeout}s (${Math.round(pending.timeout / 60)} min)` : `Default (${TIMEOUT_DEFAULT_SECONDS}s)`;
|
|
16615
|
+
const accountLabel = resolveSlotLabel(pending.backend, pending.credentialSlotId);
|
|
16383
16616
|
const lines = [
|
|
16384
16617
|
"Job configuration:",
|
|
16385
16618
|
"",
|
|
@@ -16388,6 +16621,7 @@ async function promptConfirm(chatId, channel) {
|
|
|
16388
16621
|
` Timezone: ${pending.timezone ?? "UTC"}`,
|
|
16389
16622
|
` Backend: ${backendName}`,
|
|
16390
16623
|
` Model: ${pending.model ?? "default"}`,
|
|
16624
|
+
` Account: ${accountLabel}`,
|
|
16391
16625
|
` Thinking: ${pending.thinking ?? "auto"}`,
|
|
16392
16626
|
` Timeout: ${timeoutLabel}`,
|
|
16393
16627
|
` Session: ${pending.sessionType ?? "isolated"}`,
|
|
@@ -16445,7 +16679,8 @@ ${pending.task}`, {
|
|
|
16445
16679
|
channel: pending.channel ?? null,
|
|
16446
16680
|
target: pending.target ?? null,
|
|
16447
16681
|
deliveryMode: pending.deliveryMode ?? "announce",
|
|
16448
|
-
timezone: pending.timezone ?? "UTC"
|
|
16682
|
+
timezone: pending.timezone ?? "UTC",
|
|
16683
|
+
credentialSlotId: pending.credentialSlotId ?? null
|
|
16449
16684
|
};
|
|
16450
16685
|
try {
|
|
16451
16686
|
if (editJobId) {
|
|
@@ -16507,7 +16742,8 @@ async function startEditWizard(chatId, jobId, channel) {
|
|
|
16507
16742
|
sessionType: job.sessionType,
|
|
16508
16743
|
deliveryMode: job.deliveryMode,
|
|
16509
16744
|
channel: job.channel ?? void 0,
|
|
16510
|
-
target: job.target ?? void 0
|
|
16745
|
+
target: job.target ?? void 0,
|
|
16746
|
+
credentialSlotId: job.credentialSlotId ?? void 0
|
|
16511
16747
|
};
|
|
16512
16748
|
if (pendingJobs.has(chatId)) cancelWizard(chatId);
|
|
16513
16749
|
pendingJobs.set(chatId, pending);
|
|
@@ -17273,10 +17509,11 @@ var init_live_status = __esm({
|
|
|
17273
17509
|
"use strict";
|
|
17274
17510
|
init_log();
|
|
17275
17511
|
init_helpers();
|
|
17276
|
-
|
|
17277
|
-
|
|
17512
|
+
init_telegram_throttle();
|
|
17513
|
+
FLUSH_INTERVAL_DM_MS = 2e3;
|
|
17514
|
+
FLUSH_INTERVAL_GROUP_MS = 5e3;
|
|
17278
17515
|
MAX_THINKING_CHARS = 800;
|
|
17279
|
-
GLOBAL_MIN_GAP_MS =
|
|
17516
|
+
GLOBAL_MIN_GAP_MS = 1e3;
|
|
17280
17517
|
globalLastFlushAt = 0;
|
|
17281
17518
|
TRIM_THRESHOLD = 3500;
|
|
17282
17519
|
MAX_ENTRIES = 200;
|
|
@@ -17382,6 +17619,8 @@ var init_live_status = __esm({
|
|
|
17382
17619
|
if (this.consecutiveEditFailures >= _LiveStatusMessage.MAX_EDIT_FAILURES) return;
|
|
17383
17620
|
if (Date.now() < this.nextFlushAllowedAt) return;
|
|
17384
17621
|
if (!canFlushGlobally()) return;
|
|
17622
|
+
const throttleState = getThrottleState();
|
|
17623
|
+
if (throttleState?.isPaused) return;
|
|
17385
17624
|
const deduped = dedupThinking(this.entries);
|
|
17386
17625
|
const body = renderEntries(deduped, this.modelLabel, Date.now() - this.startTime, this.hasTrimmed);
|
|
17387
17626
|
if (body === this.lastRendered) return;
|
|
@@ -19850,15 +20089,20 @@ async function sendJobDetail(chatId, jobId, channel, messageId) {
|
|
|
19850
20089
|
\u26A0\uFE0F ${job.consecutiveFailures} consecutive failures` : "";
|
|
19851
20090
|
const runs = getJobRuns(jobId, 1);
|
|
19852
20091
|
const lastRunStatus = runs.length > 0 ? runs[0].status : null;
|
|
20092
|
+
const thinking2 = job.thinking ?? "auto";
|
|
20093
|
+
const accountLabel = resolveSlotLabel(job.backend ?? void 0, job.credentialSlotId);
|
|
19853
20094
|
const lines = [
|
|
19854
20095
|
`Job #${job.id}: ${job.title ?? job.description}`,
|
|
19855
20096
|
buildSectionHeader("", 22),
|
|
19856
20097
|
...job.title ? [`Task: ${job.description}`] : [],
|
|
19857
20098
|
`Runs: ${schedule2}${tz}`,
|
|
19858
|
-
|
|
20099
|
+
"",
|
|
19859
20100
|
`Last run: ${lastRun}${lastRunStatus ? ` (${lastRunStatus})` : ""}`,
|
|
19860
20101
|
`Next run: ${nextRun}`,
|
|
19861
|
-
`Status: ${status}${failures}
|
|
20102
|
+
`Status: ${status}${failures}`,
|
|
20103
|
+
"",
|
|
20104
|
+
`\u{1F3F7} ${backend2} \xB7 ${model2} \xB7 ${thinking2}`,
|
|
20105
|
+
`\u{1F464} ${accountLabel}`
|
|
19862
20106
|
];
|
|
19863
20107
|
const text = lines.join("\n");
|
|
19864
20108
|
if (typeof channel.sendKeyboard !== "function") {
|
|
@@ -19875,6 +20119,7 @@ async function sendJobDetail(chatId, jobId, channel, messageId) {
|
|
|
19875
20119
|
}
|
|
19876
20120
|
actionRow1.push({ label: "Edit", data: `job:edit:${job.id}` });
|
|
19877
20121
|
const actionRow2 = [
|
|
20122
|
+
{ label: "\u{1F9EA} Test Model", data: `job:test:${job.id}` },
|
|
19878
20123
|
{ label: "View Runs", data: `job:runs:${job.id}`, style: "primary" }
|
|
19879
20124
|
];
|
|
19880
20125
|
if (job.active) {
|
|
@@ -20071,6 +20316,7 @@ var init_ui = __esm({
|
|
|
20071
20316
|
init_format_time();
|
|
20072
20317
|
init_cron();
|
|
20073
20318
|
init_humanize();
|
|
20319
|
+
init_wizard();
|
|
20074
20320
|
init_stt();
|
|
20075
20321
|
init_helpers();
|
|
20076
20322
|
ROTATION_MODE_LABELS = {
|
|
@@ -21933,8 +22179,8 @@ var init_types4 = __esm({
|
|
|
21933
22179
|
});
|
|
21934
22180
|
|
|
21935
22181
|
// src/council/wizard.ts
|
|
21936
|
-
var
|
|
21937
|
-
__export(
|
|
22182
|
+
var wizard_exports2 = {};
|
|
22183
|
+
__export(wizard_exports2, {
|
|
21938
22184
|
buildSelectKeyboard: () => buildSelectKeyboard,
|
|
21939
22185
|
cancelCouncil: () => cancelCouncil,
|
|
21940
22186
|
getCouncilState: () => getCouncilState,
|
|
@@ -23777,7 +24023,7 @@ async function handleCouncilCommand(chatId, commandArgs, msg, channel) {
|
|
|
23777
24023
|
await channel.sendText(chatId, "Council requires DASHBOARD_ENABLED=1 (uses the agent orchestrator).", { parseMode: "plain" });
|
|
23778
24024
|
return;
|
|
23779
24025
|
}
|
|
23780
|
-
const { startCouncilWizard: startCouncilWizard2, buildSelectKeyboard: buildSelectKeyboard2 } = await Promise.resolve().then(() => (init_wizard2(),
|
|
24026
|
+
const { startCouncilWizard: startCouncilWizard2, buildSelectKeyboard: buildSelectKeyboard2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports2));
|
|
23781
24027
|
startCouncilWizard2(chatId);
|
|
23782
24028
|
if (typeof channel.sendKeyboard === "function") {
|
|
23783
24029
|
const { text, buttons } = buildSelectKeyboard2(chatId);
|
|
@@ -24378,12 +24624,99 @@ ${plan.originalMessage}`;
|
|
|
24378
24624
|
} else if (data.startsWith("sched:")) {
|
|
24379
24625
|
await handleWizardCallback(chatId, data, channel);
|
|
24380
24626
|
} else if (data.startsWith("job:")) {
|
|
24627
|
+
async function showJobAccountPicker(cid, jobId, backend2, model2, thinking2, ch) {
|
|
24628
|
+
const isGemini = backend2 === "gemini";
|
|
24629
|
+
const slots = isGemini ? getGeminiSlots() : getBackendSlots(backend2);
|
|
24630
|
+
const enabledSlots = slots.filter((s) => s.enabled);
|
|
24631
|
+
if (enabledSlots.length === 0 || typeof ch.sendKeyboard !== "function") {
|
|
24632
|
+
const { updateJob: updateJobFields } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
24633
|
+
updateJobFields(jobId, { backend: backend2, model: model2, thinking: thinking2, credentialSlotId: null });
|
|
24634
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
24635
|
+
const adapter = getAdapter4(backend2);
|
|
24636
|
+
await ch.sendText(cid, `Job #${jobId} updated: ${adapter.displayName} / ${model2} / ${thinking2}`, { parseMode: "plain" });
|
|
24637
|
+
await sendJobDetail(cid, jobId, ch);
|
|
24638
|
+
return;
|
|
24639
|
+
}
|
|
24640
|
+
const rows = [
|
|
24641
|
+
[{ label: "\u{1F504} Auto (rotate)", data: `job:setaccount:${jobId}:${backend2}:${model2}:${thinking2}:auto` }]
|
|
24642
|
+
];
|
|
24643
|
+
for (const slot of enabledSlots) {
|
|
24644
|
+
const s = slot;
|
|
24645
|
+
const icon = s.slotType === "api_key" ? "\u{1F511}" : "\u{1F4E7}";
|
|
24646
|
+
const label2 = s.label || s.email || `Slot #${s.id}`;
|
|
24647
|
+
const type = s.slotType === "api_key" ? "API key" : "OAuth";
|
|
24648
|
+
rows.push([{ label: `${icon} ${label2} (${type})`, data: `job:setaccount:${jobId}:${backend2}:${model2}:${thinking2}:${s.id}` }]);
|
|
24649
|
+
}
|
|
24650
|
+
rows.push([{ label: "\u2190 Back", data: `job:editbackend:${jobId}` }]);
|
|
24651
|
+
await ch.sendKeyboard(cid, `Which account for Job #${jobId}?`, rows);
|
|
24652
|
+
}
|
|
24381
24653
|
const rest = data.slice(4);
|
|
24382
24654
|
if (rest === "back") {
|
|
24383
24655
|
await sendJobsBoard(chatId, channel, 1);
|
|
24384
24656
|
} else if (rest.startsWith("view:")) {
|
|
24385
24657
|
const id = parseInt(rest.slice(5), 10);
|
|
24386
24658
|
await sendJobDetail(chatId, id, channel, messageId);
|
|
24659
|
+
} else if (rest.startsWith("test:")) {
|
|
24660
|
+
const id = parseInt(rest.slice(5), 10);
|
|
24661
|
+
const { getJobById: getJob } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
24662
|
+
const testJob = getJob(id);
|
|
24663
|
+
if (!testJob) {
|
|
24664
|
+
await channel.sendText(chatId, `Job #${id} not found.`, { parseMode: "plain" });
|
|
24665
|
+
} else {
|
|
24666
|
+
await channel.sendText(chatId, `\u{1F9EA} Testing model: ${testJob.backend ?? "default"}/${testJob.model ?? "default"}...`, { parseMode: "plain" });
|
|
24667
|
+
try {
|
|
24668
|
+
const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
|
|
24669
|
+
const { BACKEND: BACKEND2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
24670
|
+
const { pinChatGeminiSlot: pinChatGeminiSlot4, pinChatBackendSlot: pinChatBackendSlot3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
24671
|
+
const testChatId = `cron:test:${id}:${Date.now()}`;
|
|
24672
|
+
const backend2 = testJob.backend ?? "claude";
|
|
24673
|
+
if (testJob.credentialSlotId) {
|
|
24674
|
+
if (backend2 === BACKEND2.GEMINI) {
|
|
24675
|
+
pinChatGeminiSlot4(testChatId, testJob.credentialSlotId);
|
|
24676
|
+
} else {
|
|
24677
|
+
pinChatBackendSlot3(testChatId, backend2, testJob.credentialSlotId);
|
|
24678
|
+
}
|
|
24679
|
+
}
|
|
24680
|
+
const t0 = Date.now();
|
|
24681
|
+
const resp = await askAgent3(testChatId, 'Reply with exactly: "pong"', {
|
|
24682
|
+
backend: backend2,
|
|
24683
|
+
model: testJob.model ?? void 0,
|
|
24684
|
+
bootstrapTier: "chat",
|
|
24685
|
+
maxTurns: 1,
|
|
24686
|
+
timeoutMs: 3e4,
|
|
24687
|
+
permMode: "yolo"
|
|
24688
|
+
});
|
|
24689
|
+
const elapsed = ((Date.now() - t0) / 1e3).toFixed(1);
|
|
24690
|
+
const got = resp.text?.trim().slice(0, 100) || "(empty)";
|
|
24691
|
+
const { resolveSlotLabel: resolveSlotLabel2 } = await Promise.resolve().then(() => (init_wizard(), wizard_exports));
|
|
24692
|
+
const acct = resolveSlotLabel2(backend2, testJob.credentialSlotId);
|
|
24693
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
24694
|
+
await channel.sendKeyboard(
|
|
24695
|
+
chatId,
|
|
24696
|
+
`\u2705 Model test passed (${elapsed}s)
|
|
24697
|
+
|
|
24698
|
+
Backend: ${backend2}
|
|
24699
|
+
Model: ${testJob.model ?? "default"}
|
|
24700
|
+
Account: ${acct}
|
|
24701
|
+
Response: "${got}"`,
|
|
24702
|
+
[[{ label: "\u2190 Back to Job", data: `job:view:${id}` }]]
|
|
24703
|
+
);
|
|
24704
|
+
}
|
|
24705
|
+
} catch (err) {
|
|
24706
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
24707
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
24708
|
+
await channel.sendKeyboard(
|
|
24709
|
+
chatId,
|
|
24710
|
+
`\u274C Model test failed
|
|
24711
|
+
|
|
24712
|
+
${msg.slice(0, 500)}`,
|
|
24713
|
+
[[{ label: "\u2190 Back to Job", data: `job:view:${id}` }]]
|
|
24714
|
+
);
|
|
24715
|
+
} else {
|
|
24716
|
+
await channel.sendText(chatId, `\u274C Model test failed: ${msg.slice(0, 300)}`, { parseMode: "plain" });
|
|
24717
|
+
}
|
|
24718
|
+
}
|
|
24719
|
+
}
|
|
24387
24720
|
} else if (rest.startsWith("run:")) {
|
|
24388
24721
|
const id = parseInt(rest.slice(4), 10);
|
|
24389
24722
|
await channel.sendText(chatId, `Triggering job #${id}...`, { parseMode: "plain" });
|
|
@@ -24522,11 +24855,42 @@ What do you want to change?`,
|
|
|
24522
24855
|
const id = parseInt(parts[0], 10);
|
|
24523
24856
|
const backend2 = parts[1];
|
|
24524
24857
|
const model2 = parts.slice(2).join(":");
|
|
24858
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
24859
|
+
const adapter = getAdapter4(backend2);
|
|
24860
|
+
const modelInfo = adapter.availableModels[model2];
|
|
24861
|
+
if (modelInfo?.thinking === "adjustable" && modelInfo.thinkingLevels && typeof channel.sendKeyboard === "function") {
|
|
24862
|
+
const rows = modelInfo.thinkingLevels.map((level) => [{
|
|
24863
|
+
label: level === "auto" ? "Auto (default)" : level.replace("_", " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
24864
|
+
data: `job:setthinking:${id}:${backend2}:${model2}:${level}`
|
|
24865
|
+
}]);
|
|
24866
|
+
rows.push([{ label: "\u2190 Back", data: `job:setbackend:${id}:${backend2}` }]);
|
|
24867
|
+
await channel.sendKeyboard(chatId, `Thinking level for Job #${id}?`, rows);
|
|
24868
|
+
} else {
|
|
24869
|
+
await showJobAccountPicker(chatId, id, backend2, model2, "auto", channel);
|
|
24870
|
+
}
|
|
24871
|
+
} else if (rest.startsWith("setthinking:")) {
|
|
24872
|
+
const parts = rest.slice(12).split(":");
|
|
24873
|
+
const id = parseInt(parts[0], 10);
|
|
24874
|
+
const backend2 = parts[1];
|
|
24875
|
+
const model2 = parts[2];
|
|
24876
|
+
const thinking2 = parts[3] ?? "auto";
|
|
24877
|
+
await showJobAccountPicker(chatId, id, backend2, model2, thinking2, channel);
|
|
24878
|
+
} else if (rest.startsWith("setaccount:")) {
|
|
24879
|
+
const parts = rest.slice(11).split(":");
|
|
24880
|
+
const id = parseInt(parts[0], 10);
|
|
24881
|
+
const backend2 = parts[1];
|
|
24882
|
+
const model2 = parts[2];
|
|
24883
|
+
const thinking2 = parts[3];
|
|
24884
|
+
const slotVal = parts[4];
|
|
24885
|
+
const credentialSlotId = slotVal === "auto" ? null : parseInt(slotVal, 10);
|
|
24525
24886
|
const { updateJob: updateJobFields } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
24526
|
-
updateJobFields(id, { backend: backend2, model: model2 });
|
|
24887
|
+
updateJobFields(id, { backend: backend2, model: model2, thinking: thinking2, credentialSlotId });
|
|
24527
24888
|
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
24528
24889
|
const adapter = getAdapter4(backend2);
|
|
24529
|
-
|
|
24890
|
+
const { resolveSlotLabel: resolveSlotLabel2 } = await Promise.resolve().then(() => (init_wizard(), wizard_exports));
|
|
24891
|
+
const accountLabel = resolveSlotLabel2(backend2, credentialSlotId);
|
|
24892
|
+
await channel.sendText(chatId, `Job #${id} updated: ${adapter.displayName} / ${model2} / ${thinking2}
|
|
24893
|
+
Account: ${accountLabel}`, { parseMode: "plain" });
|
|
24530
24894
|
await sendJobDetail(chatId, id, channel);
|
|
24531
24895
|
} else if (rest.startsWith("edittimeout:")) {
|
|
24532
24896
|
const id = parseInt(rest.slice(12), 10);
|
|
@@ -24863,7 +25227,7 @@ ${rotationNote}`, { parseMode: "html" });
|
|
|
24863
25227
|
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
24864
25228
|
const toggleAdapter = getAdapter4(backend2);
|
|
24865
25229
|
const label2 = toggleAdapter.availableModels[model2]?.label ?? model2;
|
|
24866
|
-
const { toggleParticipant: toggleParticipant2, buildSelectKeyboard: buildSelectKeyboard2, hasPendingCouncil: hasPendingCouncil2 } = await Promise.resolve().then(() => (init_wizard2(),
|
|
25230
|
+
const { toggleParticipant: toggleParticipant2, buildSelectKeyboard: buildSelectKeyboard2, hasPendingCouncil: hasPendingCouncil2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports2));
|
|
24867
25231
|
if (!hasPendingCouncil2(chatId)) {
|
|
24868
25232
|
await channel.sendText(chatId, "No council wizard active. Use /council to start.", { parseMode: "plain" });
|
|
24869
25233
|
return;
|
|
@@ -24876,7 +25240,7 @@ ${rotationNote}`, { parseMode: "html" });
|
|
|
24876
25240
|
return;
|
|
24877
25241
|
}
|
|
24878
25242
|
if (action === "start") {
|
|
24879
|
-
const { getCouncilState: getCouncilState2 } = await Promise.resolve().then(() => (init_wizard2(),
|
|
25243
|
+
const { getCouncilState: getCouncilState2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports2));
|
|
24880
25244
|
const state = getCouncilState2(chatId);
|
|
24881
25245
|
if (!state || state.selected.size < 2) {
|
|
24882
25246
|
await channel.sendText(chatId, "Select at least 2 models first.", { parseMode: "plain" });
|
|
@@ -24890,7 +25254,7 @@ Now type the question you want them to debate.`, { parseMode: "plain" });
|
|
|
24890
25254
|
return;
|
|
24891
25255
|
}
|
|
24892
25256
|
if (action === "cancel") {
|
|
24893
|
-
const { cancelCouncil: cancelCouncil2 } = await Promise.resolve().then(() => (init_wizard2(),
|
|
25257
|
+
const { cancelCouncil: cancelCouncil2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports2));
|
|
24894
25258
|
cancelCouncil2(chatId);
|
|
24895
25259
|
await channel.sendText(chatId, "Council cancelled.", { parseMode: "plain" });
|
|
24896
25260
|
return;
|
|
@@ -25937,7 +26301,7 @@ async function handleText(msg, channel) {
|
|
|
25937
26301
|
await channel.sendText(chatId, limitMsg, { parseMode: "plain" });
|
|
25938
26302
|
return;
|
|
25939
26303
|
}
|
|
25940
|
-
let intent =
|
|
26304
|
+
let intent = await classifyIntentAsync(text, chatId);
|
|
25941
26305
|
const cleanText = text.startsWith(">>") ? text.slice(2).trim() : text;
|
|
25942
26306
|
let bootstrapTier = intent === "chat" ? "chat" : void 0;
|
|
25943
26307
|
let maxTurns = void 0;
|
|
@@ -25981,9 +26345,10 @@ async function handleText(msg, channel) {
|
|
|
25981
26345
|
agentMode: effectiveAgentMode
|
|
25982
26346
|
});
|
|
25983
26347
|
if (response.text) {
|
|
25984
|
-
|
|
26348
|
+
const revisedPlan = response.text.replace(/\[REACT:.+?\]/g, "").replace(/\[SEND_FILE:.+?\]/g, "").replace(/\[GENERATE_IMAGE:.+?\]/g, "").replace(/\[HISTORY_SEARCH:[^\]]+\]/g, "").trim();
|
|
26349
|
+
storePendingPlan(chatId, revisedPlan, text);
|
|
25985
26350
|
if (typeof channel.sendKeyboard === "function") {
|
|
25986
|
-
await channel.sendKeyboard(chatId, `\u{1F50D} ${
|
|
26351
|
+
await channel.sendKeyboard(chatId, `\u{1F50D} ${revisedPlan}`, [
|
|
25987
26352
|
[
|
|
25988
26353
|
{ label: "\u2705 Approve", data: "exec:approve", style: "success" },
|
|
25989
26354
|
{ label: "\u274C Reject", data: "exec:reject", style: "danger" }
|
|
@@ -26055,7 +26420,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
|
|
|
26055
26420
|
}
|
|
26056
26421
|
}
|
|
26057
26422
|
{
|
|
26058
|
-
const { hasPendingCouncil: hasPendingCouncil2, getCouncilState: getCouncilState2, setCouncilQuestion: setCouncilQuestion2, cancelCouncil: cancelCouncil2 } = await Promise.resolve().then(() => (init_wizard2(),
|
|
26423
|
+
const { hasPendingCouncil: hasPendingCouncil2, getCouncilState: getCouncilState2, setCouncilQuestion: setCouncilQuestion2, cancelCouncil: cancelCouncil2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports2));
|
|
26059
26424
|
if (hasPendingCouncil2(chatId)) {
|
|
26060
26425
|
const state = getCouncilState2(chatId);
|
|
26061
26426
|
if (state?.step === "question") {
|
|
@@ -26119,7 +26484,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26119
26484
|
})) {
|
|
26120
26485
|
const planDirective = buildPlanningDirective();
|
|
26121
26486
|
let typingActive2 = true;
|
|
26122
|
-
const
|
|
26487
|
+
const typingLoop = async () => {
|
|
26123
26488
|
while (typingActive2) {
|
|
26124
26489
|
try {
|
|
26125
26490
|
await channel.sendTyping?.(chatId);
|
|
@@ -26128,7 +26493,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26128
26493
|
await new Promise((r) => setTimeout(r, 4e3));
|
|
26129
26494
|
}
|
|
26130
26495
|
};
|
|
26131
|
-
|
|
26496
|
+
typingLoop().catch(() => {
|
|
26132
26497
|
});
|
|
26133
26498
|
try {
|
|
26134
26499
|
const planResponse = await askAgent(chatId, cleanText || text, {
|
|
@@ -26169,18 +26534,23 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26169
26534
|
}
|
|
26170
26535
|
return;
|
|
26171
26536
|
}
|
|
26172
|
-
|
|
26173
|
-
const
|
|
26174
|
-
|
|
26175
|
-
|
|
26176
|
-
|
|
26177
|
-
|
|
26537
|
+
const verboseForTyping = settings.getVerboseLevel();
|
|
26538
|
+
const showThinkingForTyping = settings.getShowThinkingUi();
|
|
26539
|
+
const needsLiveStatusForTyping = verboseForTyping !== "off" || showThinkingForTyping;
|
|
26540
|
+
let typingActive = !needsLiveStatusForTyping;
|
|
26541
|
+
if (typingActive) {
|
|
26542
|
+
const typingLoop = async () => {
|
|
26543
|
+
while (typingActive) {
|
|
26544
|
+
try {
|
|
26545
|
+
await channel.sendTyping?.(chatId);
|
|
26546
|
+
} catch {
|
|
26547
|
+
}
|
|
26548
|
+
await new Promise((r) => setTimeout(r, 4e3));
|
|
26178
26549
|
}
|
|
26179
|
-
|
|
26180
|
-
|
|
26181
|
-
|
|
26182
|
-
|
|
26183
|
-
});
|
|
26550
|
+
};
|
|
26551
|
+
typingLoop().catch(() => {
|
|
26552
|
+
});
|
|
26553
|
+
}
|
|
26184
26554
|
try {
|
|
26185
26555
|
const tMode = settings.getMode();
|
|
26186
26556
|
const tVerbose = settings.getVerboseLevel();
|
|
@@ -26803,6 +27173,13 @@ async function runWithRetry(job, model2, runId, t0) {
|
|
|
26803
27173
|
const cronBackend = currentBackend;
|
|
26804
27174
|
setAllowPaidSlots(chatId, cronBackend);
|
|
26805
27175
|
}
|
|
27176
|
+
if (job.credentialSlotId) {
|
|
27177
|
+
if (currentBackend === BACKEND.GEMINI) {
|
|
27178
|
+
pinChatGeminiSlot(chatId, job.credentialSlotId);
|
|
27179
|
+
} else {
|
|
27180
|
+
pinChatBackendSlot(chatId, currentBackend, job.credentialSlotId);
|
|
27181
|
+
}
|
|
27182
|
+
}
|
|
26806
27183
|
const response = await askAgent(chatId, job.description, {
|
|
26807
27184
|
model: currentModel,
|
|
26808
27185
|
backend: currentBackend,
|
|
@@ -26874,6 +27251,7 @@ var init_cron = __esm({
|
|
|
26874
27251
|
"src/scheduler/cron.ts"() {
|
|
26875
27252
|
"use strict";
|
|
26876
27253
|
init_store5();
|
|
27254
|
+
init_types();
|
|
26877
27255
|
init_backends();
|
|
26878
27256
|
init_agent();
|
|
26879
27257
|
init_log();
|
|
@@ -27564,6 +27942,115 @@ ${body.replace(/<[^>]*>/g, "").trim()}</code>
|
|
|
27564
27942
|
}
|
|
27565
27943
|
});
|
|
27566
27944
|
|
|
27945
|
+
// src/channels/health.ts
|
|
27946
|
+
function trackChannel(channel) {
|
|
27947
|
+
healthState.set(channel.name, {
|
|
27948
|
+
status: "healthy",
|
|
27949
|
+
startedAt: Date.now(),
|
|
27950
|
+
lastHealthyAt: Date.now(),
|
|
27951
|
+
lastErrorAt: null,
|
|
27952
|
+
lastError: null,
|
|
27953
|
+
reconnectAttempts: 0
|
|
27954
|
+
});
|
|
27955
|
+
}
|
|
27956
|
+
function markChannelHealthy(name) {
|
|
27957
|
+
const state = healthState.get(name);
|
|
27958
|
+
if (state) {
|
|
27959
|
+
state.status = "healthy";
|
|
27960
|
+
state.lastHealthyAt = Date.now();
|
|
27961
|
+
state.reconnectAttempts = 0;
|
|
27962
|
+
}
|
|
27963
|
+
}
|
|
27964
|
+
function markChannelDown(name, error3) {
|
|
27965
|
+
const state = healthState.get(name);
|
|
27966
|
+
if (state) {
|
|
27967
|
+
state.status = "down";
|
|
27968
|
+
state.lastErrorAt = Date.now();
|
|
27969
|
+
state.lastError = error3;
|
|
27970
|
+
warn(`[channel-health] ${name} marked as down: ${error3}`);
|
|
27971
|
+
}
|
|
27972
|
+
}
|
|
27973
|
+
function getChannelHealth(name) {
|
|
27974
|
+
const state = healthState.get(name);
|
|
27975
|
+
if (!state) return null;
|
|
27976
|
+
return {
|
|
27977
|
+
name,
|
|
27978
|
+
status: state.status,
|
|
27979
|
+
lastHealthyAt: state.lastHealthyAt,
|
|
27980
|
+
lastErrorAt: state.lastErrorAt,
|
|
27981
|
+
lastError: state.lastError,
|
|
27982
|
+
reconnectAttempts: state.reconnectAttempts,
|
|
27983
|
+
uptimeMs: state.status === "healthy" ? Date.now() - state.startedAt : 0
|
|
27984
|
+
};
|
|
27985
|
+
}
|
|
27986
|
+
async function attemptReconnect(channel, handler) {
|
|
27987
|
+
const state = healthState.get(channel.name);
|
|
27988
|
+
if (!state) return false;
|
|
27989
|
+
if (reconnecting.has(channel.name)) {
|
|
27990
|
+
log(`[channel-health] ${channel.name}: reconnect already in progress, skipping`);
|
|
27991
|
+
return false;
|
|
27992
|
+
}
|
|
27993
|
+
if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
27994
|
+
error(`[channel-health] ${channel.name}: max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached`);
|
|
27995
|
+
return false;
|
|
27996
|
+
}
|
|
27997
|
+
reconnecting.add(channel.name);
|
|
27998
|
+
state.reconnectAttempts++;
|
|
27999
|
+
const backoffMs = RECONNECT_BASE_MS * Math.pow(2, state.reconnectAttempts - 1);
|
|
28000
|
+
log(`[channel-health] ${channel.name}: reconnect attempt ${state.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS} in ${backoffMs}ms`);
|
|
28001
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
28002
|
+
try {
|
|
28003
|
+
await channel.stop().catch(() => {
|
|
28004
|
+
});
|
|
28005
|
+
await channel.start(handler);
|
|
28006
|
+
markChannelHealthy(channel.name);
|
|
28007
|
+
log(`[channel-health] ${channel.name}: reconnected successfully`);
|
|
28008
|
+
return true;
|
|
28009
|
+
} catch (err) {
|
|
28010
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28011
|
+
markChannelDown(channel.name, msg);
|
|
28012
|
+
return false;
|
|
28013
|
+
} finally {
|
|
28014
|
+
reconnecting.delete(channel.name);
|
|
28015
|
+
}
|
|
28016
|
+
}
|
|
28017
|
+
function startHealthMonitor3(channels, handler) {
|
|
28018
|
+
registeredHandler = handler;
|
|
28019
|
+
for (const ch of channels) {
|
|
28020
|
+
trackChannel(ch);
|
|
28021
|
+
}
|
|
28022
|
+
healthInterval = setInterval(() => {
|
|
28023
|
+
for (const ch of channels) {
|
|
28024
|
+
const health = getChannelHealth(ch.name);
|
|
28025
|
+
if (health?.status === "down" && registeredHandler) {
|
|
28026
|
+
attemptReconnect(ch, registeredHandler).catch(() => {
|
|
28027
|
+
});
|
|
28028
|
+
}
|
|
28029
|
+
}
|
|
28030
|
+
}, HEALTH_CHECK_INTERVAL_MS);
|
|
28031
|
+
log(`[channel-health] Monitoring ${channels.length} channel(s)`);
|
|
28032
|
+
}
|
|
28033
|
+
function stopHealthMonitor3() {
|
|
28034
|
+
if (healthInterval) {
|
|
28035
|
+
clearInterval(healthInterval);
|
|
28036
|
+
healthInterval = null;
|
|
28037
|
+
}
|
|
28038
|
+
}
|
|
28039
|
+
var healthState, MAX_RECONNECT_ATTEMPTS, RECONNECT_BASE_MS, HEALTH_CHECK_INTERVAL_MS, healthInterval, registeredHandler, reconnecting;
|
|
28040
|
+
var init_health3 = __esm({
|
|
28041
|
+
"src/channels/health.ts"() {
|
|
28042
|
+
"use strict";
|
|
28043
|
+
init_log();
|
|
28044
|
+
healthState = /* @__PURE__ */ new Map();
|
|
28045
|
+
MAX_RECONNECT_ATTEMPTS = 5;
|
|
28046
|
+
RECONNECT_BASE_MS = 5e3;
|
|
28047
|
+
HEALTH_CHECK_INTERVAL_MS = 15e3;
|
|
28048
|
+
healthInterval = null;
|
|
28049
|
+
registeredHandler = null;
|
|
28050
|
+
reconnecting = /* @__PURE__ */ new Set();
|
|
28051
|
+
}
|
|
28052
|
+
});
|
|
28053
|
+
|
|
27567
28054
|
// src/channels/telegram.ts
|
|
27568
28055
|
import { API_CONSTANTS, Bot, GrammyError as GrammyError2, InlineKeyboard, InputFile } from "grammy";
|
|
27569
28056
|
function isFastPathMessage(msg) {
|
|
@@ -27594,10 +28081,11 @@ var init_telegram2 = __esm({
|
|
|
27594
28081
|
"use strict";
|
|
27595
28082
|
init_telegram();
|
|
27596
28083
|
init_log();
|
|
28084
|
+
init_health3();
|
|
27597
28085
|
init_store5();
|
|
27598
28086
|
init_telegram_throttle();
|
|
27599
28087
|
FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
|
|
27600
|
-
TelegramChannel = class {
|
|
28088
|
+
TelegramChannel = class _TelegramChannel {
|
|
27601
28089
|
name = "telegram";
|
|
27602
28090
|
bot;
|
|
27603
28091
|
allowedChatIds;
|
|
@@ -27607,6 +28095,19 @@ var init_telegram2 = __esm({
|
|
|
27607
28095
|
// messageId → chatId
|
|
27608
28096
|
reactionHandlers = [];
|
|
27609
28097
|
throttle;
|
|
28098
|
+
// ── Polling health tracking ─────────────────────────────────────────
|
|
28099
|
+
/** Timestamp of last update received from Telegram (message, callback, reaction) */
|
|
28100
|
+
lastUpdateAt = 0;
|
|
28101
|
+
/** True while polling is expected to be active (between start() and stop()) */
|
|
28102
|
+
pollingExpected = false;
|
|
28103
|
+
/** Watchdog interval that detects silent polling death */
|
|
28104
|
+
pollingWatchdog = null;
|
|
28105
|
+
/** Max time without any update before we consider polling dead (ms) */
|
|
28106
|
+
static POLLING_SILENCE_THRESHOLD_MS = 2 * 60 * 1e3;
|
|
28107
|
+
// 2 minutes
|
|
28108
|
+
/** How often the watchdog checks for polling health (ms) */
|
|
28109
|
+
static POLLING_WATCHDOG_INTERVAL_MS = 60 * 1e3;
|
|
28110
|
+
// 60 seconds
|
|
27610
28111
|
constructor() {
|
|
27611
28112
|
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
27612
28113
|
if (!token) {
|
|
@@ -27624,6 +28125,10 @@ var init_telegram2 = __esm({
|
|
|
27624
28125
|
this.bot = new Bot(token);
|
|
27625
28126
|
this.throttle = new TelegramThrottle();
|
|
27626
28127
|
this.throttle.setResumeNotifier(async (chatId, pausedSec, queuedCount) => {
|
|
28128
|
+
if (pausedSec > 60) {
|
|
28129
|
+
log(`[telegram] Skipping resume notification (paused ${pausedSec}s \u2014 too long, would risk another 429)`);
|
|
28130
|
+
return;
|
|
28131
|
+
}
|
|
27627
28132
|
try {
|
|
27628
28133
|
await this.bot.api.sendMessage(
|
|
27629
28134
|
numericChatId(chatId),
|
|
@@ -27735,6 +28240,7 @@ var init_telegram2 = __esm({
|
|
|
27735
28240
|
{ command: "council", description: "Multi-model debate (select models, anonymous rounds)" }
|
|
27736
28241
|
]);
|
|
27737
28242
|
this.bot.on("message", async (ctx) => {
|
|
28243
|
+
this.lastUpdateAt = Date.now();
|
|
27738
28244
|
const chatId = ctx.chat.id.toString();
|
|
27739
28245
|
const senderId = ctx.from?.id?.toString() ?? "";
|
|
27740
28246
|
const authorized = this.isAuthorized(chatId) || this.isAuthorized(senderId);
|
|
@@ -27762,6 +28268,7 @@ var init_telegram2 = __esm({
|
|
|
27762
28268
|
});
|
|
27763
28269
|
});
|
|
27764
28270
|
this.bot.on("callback_query:data", (ctx) => {
|
|
28271
|
+
this.lastUpdateAt = Date.now();
|
|
27765
28272
|
const userId = ctx.from.id.toString();
|
|
27766
28273
|
const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
|
|
27767
28274
|
log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
|
|
@@ -27789,6 +28296,7 @@ var init_telegram2 = __esm({
|
|
|
27789
28296
|
});
|
|
27790
28297
|
});
|
|
27791
28298
|
this.bot.on("message_reaction", async (ctx) => {
|
|
28299
|
+
this.lastUpdateAt = Date.now();
|
|
27792
28300
|
const chatId = String(ctx.chat.id);
|
|
27793
28301
|
const messageId = ctx.messageReaction.message_id;
|
|
27794
28302
|
if (!this.agentMessageIds.has(messageId)) return;
|
|
@@ -27805,6 +28313,7 @@ var init_telegram2 = __esm({
|
|
|
27805
28313
|
}
|
|
27806
28314
|
});
|
|
27807
28315
|
this.bot.on("inline_query", (ctx) => {
|
|
28316
|
+
this.lastUpdateAt = Date.now();
|
|
27808
28317
|
if (!this.isAuthorized(ctx.from.id.toString())) return;
|
|
27809
28318
|
this.handleInlineQuery(ctx).catch((err) => {
|
|
27810
28319
|
error("[telegram] Inline query error:", err);
|
|
@@ -27818,25 +28327,64 @@ var init_telegram2 = __esm({
|
|
|
27818
28327
|
error("[telegram] Unhandled error:", err);
|
|
27819
28328
|
}
|
|
27820
28329
|
});
|
|
27821
|
-
this.
|
|
28330
|
+
this.pollingExpected = true;
|
|
28331
|
+
this.lastUpdateAt = Date.now();
|
|
28332
|
+
const pollingPromise = this.bot.start({
|
|
27822
28333
|
allowed_updates: [...API_CONSTANTS.ALL_UPDATE_TYPES],
|
|
27823
28334
|
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
28335
|
});
|
|
28336
|
+
pollingPromise.then(
|
|
28337
|
+
() => {
|
|
28338
|
+
if (this.pollingExpected) {
|
|
28339
|
+
error("[telegram] CRITICAL: Polling loop exited unexpectedly (resolved without stop)");
|
|
28340
|
+
markChannelDown("telegram", "Polling loop exited unexpectedly");
|
|
28341
|
+
}
|
|
28342
|
+
},
|
|
28343
|
+
(err) => {
|
|
28344
|
+
if (this.pollingExpected) {
|
|
28345
|
+
error("[telegram] Fatal: bot.start() failed:", err);
|
|
28346
|
+
error("[telegram] Check TELEGRAM_BOT_TOKEN in ~/.cc-claw/.env \u2014 it may be invalid or revoked.");
|
|
28347
|
+
error("[telegram] To regenerate: message @BotFather on Telegram, use /revoke, then 'cc-claw setup'");
|
|
28348
|
+
markChannelDown("telegram", `Polling error: ${err instanceof Error ? err.message : String(err)}`);
|
|
28349
|
+
}
|
|
28350
|
+
}
|
|
28351
|
+
);
|
|
28352
|
+
this.pollingWatchdog = setInterval(() => {
|
|
28353
|
+
if (!this.pollingExpected) return;
|
|
28354
|
+
const silenceMs = Date.now() - this.lastUpdateAt;
|
|
28355
|
+
if (silenceMs > _TelegramChannel.POLLING_SILENCE_THRESHOLD_MS) {
|
|
28356
|
+
error(
|
|
28357
|
+
`[telegram] CRITICAL: No updates received for ${Math.round(silenceMs / 1e3)}s \u2014 polling likely dead, triggering reconnect`
|
|
28358
|
+
);
|
|
28359
|
+
markChannelDown("telegram", `No updates for ${Math.round(silenceMs / 1e3)}s`);
|
|
28360
|
+
this.lastUpdateAt = Date.now();
|
|
28361
|
+
}
|
|
28362
|
+
}, _TelegramChannel.POLLING_WATCHDOG_INTERVAL_MS);
|
|
27830
28363
|
}
|
|
27831
28364
|
async stop() {
|
|
27832
|
-
|
|
28365
|
+
this.pollingExpected = false;
|
|
28366
|
+
if (this.pollingWatchdog) {
|
|
28367
|
+
clearInterval(this.pollingWatchdog);
|
|
28368
|
+
this.pollingWatchdog = null;
|
|
28369
|
+
}
|
|
28370
|
+
try {
|
|
28371
|
+
await this.bot.stop();
|
|
28372
|
+
} catch {
|
|
28373
|
+
}
|
|
28374
|
+
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
28375
|
+
if (token) {
|
|
28376
|
+
this.bot = new Bot(token);
|
|
28377
|
+
}
|
|
27833
28378
|
}
|
|
27834
28379
|
async sendTyping(chatId, threadId) {
|
|
27835
|
-
if (this.throttle.isPaused()) return;
|
|
27836
28380
|
try {
|
|
27837
|
-
await this.
|
|
27838
|
-
|
|
27839
|
-
|
|
28381
|
+
await this.throttle.tryBestEffort(
|
|
28382
|
+
chatId,
|
|
28383
|
+
"typing",
|
|
28384
|
+
() => this.bot.api.sendChatAction(numericChatId(chatId), "typing", {
|
|
28385
|
+
...threadId ? { message_thread_id: threadId } : {}
|
|
28386
|
+
})
|
|
28387
|
+
);
|
|
27840
28388
|
} catch {
|
|
27841
28389
|
}
|
|
27842
28390
|
}
|
|
@@ -27941,7 +28489,12 @@ var init_telegram2 = __esm({
|
|
|
27941
28489
|
);
|
|
27942
28490
|
return true;
|
|
27943
28491
|
} catch (err) {
|
|
27944
|
-
|
|
28492
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
28493
|
+
if (errMsg.includes("overflow") || errMsg.includes("rate limit") || errMsg.includes("max wait")) {
|
|
28494
|
+
warn("[telegram] editText skipped fallback (throttle overload):", errMsg);
|
|
28495
|
+
return false;
|
|
28496
|
+
}
|
|
28497
|
+
warn("[telegram] editText HTML failed, trying plain fallback:", errMsg);
|
|
27945
28498
|
try {
|
|
27946
28499
|
await this.throttle.send(
|
|
27947
28500
|
chatId,
|
|
@@ -28079,9 +28632,13 @@ var init_telegram2 = __esm({
|
|
|
28079
28632
|
}
|
|
28080
28633
|
async reactToMessage(chatId, messageId, emoji) {
|
|
28081
28634
|
try {
|
|
28082
|
-
await this.
|
|
28083
|
-
|
|
28084
|
-
|
|
28635
|
+
await this.throttle.tryBestEffort(
|
|
28636
|
+
chatId,
|
|
28637
|
+
"reaction",
|
|
28638
|
+
() => this.bot.api.setMessageReaction(numericChatId(chatId), parseInt(messageId), [
|
|
28639
|
+
{ type: "emoji", emoji }
|
|
28640
|
+
])
|
|
28641
|
+
);
|
|
28085
28642
|
} catch (err) {
|
|
28086
28643
|
log(`[telegram] reactToMessage failed (chat=${chatId} msg=${messageId}): ${err}`);
|
|
28087
28644
|
}
|
|
@@ -28499,115 +29056,6 @@ var init_bootstrap2 = __esm({
|
|
|
28499
29056
|
}
|
|
28500
29057
|
});
|
|
28501
29058
|
|
|
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
29059
|
// src/cli/commands/ai-skill.ts
|
|
28612
29060
|
var ai_skill_exports = {};
|
|
28613
29061
|
__export(ai_skill_exports, {
|
package/package.json
CHANGED