cc-claw 0.20.13 → 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 +519 -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.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, {
|
|
@@ -8088,8 +8104,11 @@ function revokeSubAgentToken(agentId) {
|
|
|
8088
8104
|
function authenticateRequest(req, url) {
|
|
8089
8105
|
const authHeader = req.headers.authorization ?? "";
|
|
8090
8106
|
const bearerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
8091
|
-
const
|
|
8092
|
-
const
|
|
8107
|
+
const queryToken = url.searchParams.get("token") ?? "";
|
|
8108
|
+
const isBrowserRoute = BROWSER_ROUTES.has(url.pathname) || url.pathname.startsWith("/files");
|
|
8109
|
+
const effectiveToken = bearerToken || (isBrowserRoute ? queryToken : "");
|
|
8110
|
+
const isMainToken = effectiveToken === DASHBOARD_TOKEN;
|
|
8111
|
+
const subEntry = subAgentTokens.get(effectiveToken);
|
|
8093
8112
|
const isSubAgentToken = !!subEntry && subEntry.expiresAt > Date.now();
|
|
8094
8113
|
if (!isMainToken && !isSubAgentToken) {
|
|
8095
8114
|
return { authenticated: false, isSubAgent: false };
|
|
@@ -8136,7 +8155,7 @@ function validateAgentIdentity(req, body) {
|
|
|
8136
8155
|
throw new Error(`IDENTITY_MISMATCH: Token bound to agent ${boundAgentId}, but request claims ${callerAgentId}`);
|
|
8137
8156
|
}
|
|
8138
8157
|
}
|
|
8139
|
-
var PORT, DASHBOARD_TOKEN, SUB_AGENT_TOKEN_TTL_MS, subAgentTokens, SUB_AGENT_ALLOWED_PATHS, MAX_BODY_BYTES;
|
|
8158
|
+
var PORT, DASHBOARD_TOKEN, SUB_AGENT_TOKEN_TTL_MS, subAgentTokens, SUB_AGENT_ALLOWED_PATHS, BROWSER_ROUTES, MAX_BODY_BYTES;
|
|
8140
8159
|
var init_middleware = __esm({
|
|
8141
8160
|
"src/dashboard/middleware.ts"() {
|
|
8142
8161
|
"use strict";
|
|
@@ -8172,6 +8191,7 @@ var init_middleware = __esm({
|
|
|
8172
8191
|
"/api/memory/history",
|
|
8173
8192
|
"/api/memory/summaries"
|
|
8174
8193
|
]);
|
|
8194
|
+
BROWSER_ROUTES = /* @__PURE__ */ new Set(["/", "/index.html", "/upload"]);
|
|
8175
8195
|
MAX_BODY_BYTES = 1048576;
|
|
8176
8196
|
}
|
|
8177
8197
|
});
|
|
@@ -14974,8 +14994,8 @@ var init_telegram_throttle = __esm({
|
|
|
14974
14994
|
"src/channels/telegram-throttle.ts"() {
|
|
14975
14995
|
"use strict";
|
|
14976
14996
|
init_log();
|
|
14977
|
-
PER_CHAT_INTERVAL_MS =
|
|
14978
|
-
GLOBAL_INTERVAL_MS =
|
|
14997
|
+
PER_CHAT_INTERVAL_MS = 1e3;
|
|
14998
|
+
GLOBAL_INTERVAL_MS = 100;
|
|
14979
14999
|
MAX_RETRIES2 = 2;
|
|
14980
15000
|
RETRY_DELAY_MS = 1e3;
|
|
14981
15001
|
MAX_QUEUE_SIZE = 100;
|
|
@@ -15003,6 +15023,9 @@ var init_telegram_throttle = __esm({
|
|
|
15003
15023
|
}
|
|
15004
15024
|
/** Enqueue a Telegram API call with automatic pacing and 429 handling. */
|
|
15005
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
|
+
}
|
|
15006
15029
|
return new Promise((resolve, reject) => {
|
|
15007
15030
|
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
15008
15031
|
const dropped = this.queue.shift();
|
|
@@ -15015,6 +15038,31 @@ var init_telegram_throttle = __esm({
|
|
|
15015
15038
|
this.drain();
|
|
15016
15039
|
});
|
|
15017
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
|
+
}
|
|
15018
15066
|
/** Check whether the throttle is currently paused (rate-limited). */
|
|
15019
15067
|
isPaused() {
|
|
15020
15068
|
return Date.now() < this.pausedUntil;
|
|
@@ -15097,12 +15145,13 @@ var init_telegram_throttle = __esm({
|
|
|
15097
15145
|
// ── Pause management ────────────────────────────────────────────────
|
|
15098
15146
|
enterPause(retrySec, failedItem) {
|
|
15099
15147
|
this.queue.unshift(failedItem);
|
|
15100
|
-
|
|
15148
|
+
const bufferedSec = Math.ceil(retrySec * 1.5);
|
|
15149
|
+
this.pausedUntil = Date.now() + bufferedSec * 1e3;
|
|
15101
15150
|
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
15102
15151
|
for (const qi of this.queue) {
|
|
15103
15152
|
this.chatsPendingNotification.add(qi.chatId);
|
|
15104
15153
|
}
|
|
15105
|
-
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)`);
|
|
15106
15155
|
}
|
|
15107
15156
|
async sendResumeNotifications() {
|
|
15108
15157
|
const chats2 = new Set(this.chatsPendingNotification);
|
|
@@ -15735,6 +15784,7 @@ var init_profile = __esm({
|
|
|
15735
15784
|
var classify_exports = {};
|
|
15736
15785
|
__export(classify_exports, {
|
|
15737
15786
|
classifyIntent: () => classifyIntent,
|
|
15787
|
+
classifyIntentAsync: () => classifyIntentAsync,
|
|
15738
15788
|
getIntentStats: () => getIntentStats,
|
|
15739
15789
|
resetIntentStats: () => resetIntentStats
|
|
15740
15790
|
});
|
|
@@ -15745,12 +15795,17 @@ function resetIntentStats() {
|
|
|
15745
15795
|
intentCounts.chat = 0;
|
|
15746
15796
|
intentCounts.agentic = 0;
|
|
15747
15797
|
}
|
|
15748
|
-
function
|
|
15798
|
+
function classifyIntentFast(text, chatId) {
|
|
15749
15799
|
const trimmed = text.trim();
|
|
15750
15800
|
if (trimmed.startsWith(">>")) return "agentic";
|
|
15751
15801
|
if (trimmed.startsWith("/")) return "agentic";
|
|
15752
15802
|
const lower = trimmed.toLowerCase();
|
|
15753
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
|
+
}
|
|
15754
15809
|
const sessionId = getSessionId(chatId);
|
|
15755
15810
|
if (sessionId) {
|
|
15756
15811
|
const lastTs = getLastMessageTimestamp(chatId);
|
|
@@ -15758,47 +15813,148 @@ function classifyIntent(text, chatId) {
|
|
|
15758
15813
|
const elapsed = Date.now() - lastTs;
|
|
15759
15814
|
if (elapsed < 12e4 && trimmed.length < 30) {
|
|
15760
15815
|
log(`[intent] "${trimmed.slice(0, 30)}" -> agentic (active session, ${(elapsed / 1e3).toFixed(0)}s ago)`);
|
|
15761
|
-
intentCounts.agentic++;
|
|
15762
15816
|
return "agentic";
|
|
15763
15817
|
}
|
|
15764
15818
|
}
|
|
15765
15819
|
}
|
|
15766
|
-
if (CHAT_EXACT.has(lower)) {
|
|
15767
|
-
log(`[intent] "${lower}" -> chat (exact match)`);
|
|
15768
|
-
intentCounts.chat++;
|
|
15769
|
-
return "chat";
|
|
15770
|
-
}
|
|
15771
15820
|
if (trimmed.length <= 4 && /^[\p{Emoji}\s]+$/u.test(trimmed)) {
|
|
15772
15821
|
log(`[intent] "${trimmed}" -> chat (emoji-only)`);
|
|
15773
|
-
intentCounts.chat++;
|
|
15774
15822
|
return "chat";
|
|
15775
15823
|
}
|
|
15776
15824
|
for (const pattern of STRUCTURAL_PATTERNS) {
|
|
15777
15825
|
if (pattern.test(normalized)) {
|
|
15778
15826
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (structural: ${pattern})`);
|
|
15779
|
-
intentCounts.agentic++;
|
|
15780
15827
|
return "agentic";
|
|
15781
15828
|
}
|
|
15782
15829
|
}
|
|
15783
15830
|
for (const pattern of MUTATION_PATTERNS) {
|
|
15784
15831
|
if (pattern.test(normalized)) {
|
|
15785
15832
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (mutation: ${pattern})`);
|
|
15786
|
-
intentCounts.agentic++;
|
|
15787
15833
|
return "agentic";
|
|
15788
15834
|
}
|
|
15789
15835
|
}
|
|
15790
15836
|
for (const pattern of CHAT_QUESTION_PATTERNS) {
|
|
15791
15837
|
if (pattern.test(normalized)) {
|
|
15792
15838
|
log(`[intent] "${trimmed.slice(0, 40)}..." -> chat (question: ${pattern})`);
|
|
15793
|
-
intentCounts.chat++;
|
|
15794
15839
|
return "chat";
|
|
15795
15840
|
}
|
|
15796
15841
|
}
|
|
15797
|
-
|
|
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)`);
|
|
15798
15954
|
intentCounts.agentic++;
|
|
15799
15955
|
return "agentic";
|
|
15800
15956
|
}
|
|
15801
|
-
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;
|
|
15802
15958
|
var init_classify = __esm({
|
|
15803
15959
|
"src/intent/classify.ts"() {
|
|
15804
15960
|
"use strict";
|
|
@@ -15894,6 +16050,13 @@ var init_classify = __esm({
|
|
|
15894
16050
|
/\b(error|bug|crash|fail|broken|issue|problem|exception|stack\s?trace)\b/i,
|
|
15895
16051
|
/\b(function|class|const|let|var|import|export|return|async|await)\b/i
|
|
15896
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;
|
|
15897
16060
|
}
|
|
15898
16061
|
});
|
|
15899
16062
|
|
|
@@ -16120,8 +16283,8 @@ async function handleWizardText(chatId, text, channel) {
|
|
|
16120
16283
|
case "thinking": {
|
|
16121
16284
|
const level = text.trim().toLowerCase();
|
|
16122
16285
|
pending.thinking = level || "auto";
|
|
16123
|
-
pending.step = "
|
|
16124
|
-
await
|
|
16286
|
+
pending.step = "account";
|
|
16287
|
+
await promptAccount(chatId, channel);
|
|
16125
16288
|
break;
|
|
16126
16289
|
}
|
|
16127
16290
|
case "timeout": {
|
|
@@ -16214,6 +16377,17 @@ async function handleWizardCallback(chatId, data, channel) {
|
|
|
16214
16377
|
await promptThinking(chatId, channel);
|
|
16215
16378
|
} else if (data.startsWith("sched:thinking:")) {
|
|
16216
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" });
|
|
16217
16391
|
pending.step = "timeout";
|
|
16218
16392
|
await promptTimeout(chatId, channel);
|
|
16219
16393
|
} else if (data.startsWith("sched:timeout:")) {
|
|
@@ -16319,9 +16493,52 @@ async function promptThinking(chatId, channel) {
|
|
|
16319
16493
|
await channel.sendKeyboard(chatId, "Thinking/effort level for this job?", buttons);
|
|
16320
16494
|
} else {
|
|
16321
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;
|
|
16322
16512
|
pending.step = "timeout";
|
|
16323
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;
|
|
16324
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})`;
|
|
16325
16542
|
}
|
|
16326
16543
|
async function promptTimeout(chatId, channel) {
|
|
16327
16544
|
if (typeof channel.sendKeyboard !== "function") {
|
|
@@ -16376,6 +16593,7 @@ async function promptConfirm(chatId, channel) {
|
|
|
16376
16593
|
if (!pending) return;
|
|
16377
16594
|
const backendName = pending.backend ? getAdapter(pending.backend).displayName : "default";
|
|
16378
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);
|
|
16379
16597
|
const lines = [
|
|
16380
16598
|
"Job configuration:",
|
|
16381
16599
|
"",
|
|
@@ -16384,6 +16602,7 @@ async function promptConfirm(chatId, channel) {
|
|
|
16384
16602
|
` Timezone: ${pending.timezone ?? "UTC"}`,
|
|
16385
16603
|
` Backend: ${backendName}`,
|
|
16386
16604
|
` Model: ${pending.model ?? "default"}`,
|
|
16605
|
+
` Account: ${accountLabel}`,
|
|
16387
16606
|
` Thinking: ${pending.thinking ?? "auto"}`,
|
|
16388
16607
|
` Timeout: ${timeoutLabel}`,
|
|
16389
16608
|
` Session: ${pending.sessionType ?? "isolated"}`,
|
|
@@ -16441,7 +16660,8 @@ ${pending.task}`, {
|
|
|
16441
16660
|
channel: pending.channel ?? null,
|
|
16442
16661
|
target: pending.target ?? null,
|
|
16443
16662
|
deliveryMode: pending.deliveryMode ?? "announce",
|
|
16444
|
-
timezone: pending.timezone ?? "UTC"
|
|
16663
|
+
timezone: pending.timezone ?? "UTC",
|
|
16664
|
+
credentialSlotId: pending.credentialSlotId ?? null
|
|
16445
16665
|
};
|
|
16446
16666
|
try {
|
|
16447
16667
|
if (editJobId) {
|
|
@@ -16503,7 +16723,8 @@ async function startEditWizard(chatId, jobId, channel) {
|
|
|
16503
16723
|
sessionType: job.sessionType,
|
|
16504
16724
|
deliveryMode: job.deliveryMode,
|
|
16505
16725
|
channel: job.channel ?? void 0,
|
|
16506
|
-
target: job.target ?? void 0
|
|
16726
|
+
target: job.target ?? void 0,
|
|
16727
|
+
credentialSlotId: job.credentialSlotId ?? void 0
|
|
16507
16728
|
};
|
|
16508
16729
|
if (pendingJobs.has(chatId)) cancelWizard(chatId);
|
|
16509
16730
|
pendingJobs.set(chatId, pending);
|
|
@@ -17269,10 +17490,11 @@ var init_live_status = __esm({
|
|
|
17269
17490
|
"use strict";
|
|
17270
17491
|
init_log();
|
|
17271
17492
|
init_helpers();
|
|
17272
|
-
|
|
17273
|
-
|
|
17493
|
+
init_telegram_throttle();
|
|
17494
|
+
FLUSH_INTERVAL_DM_MS = 2e3;
|
|
17495
|
+
FLUSH_INTERVAL_GROUP_MS = 5e3;
|
|
17274
17496
|
MAX_THINKING_CHARS = 800;
|
|
17275
|
-
GLOBAL_MIN_GAP_MS =
|
|
17497
|
+
GLOBAL_MIN_GAP_MS = 1e3;
|
|
17276
17498
|
globalLastFlushAt = 0;
|
|
17277
17499
|
TRIM_THRESHOLD = 3500;
|
|
17278
17500
|
MAX_ENTRIES = 200;
|
|
@@ -17378,6 +17600,8 @@ var init_live_status = __esm({
|
|
|
17378
17600
|
if (this.consecutiveEditFailures >= _LiveStatusMessage.MAX_EDIT_FAILURES) return;
|
|
17379
17601
|
if (Date.now() < this.nextFlushAllowedAt) return;
|
|
17380
17602
|
if (!canFlushGlobally()) return;
|
|
17603
|
+
const throttleState = getThrottleState();
|
|
17604
|
+
if (throttleState?.isPaused) return;
|
|
17381
17605
|
const deduped = dedupThinking(this.entries);
|
|
17382
17606
|
const body = renderEntries(deduped, this.modelLabel, Date.now() - this.startTime, this.hasTrimmed);
|
|
17383
17607
|
if (body === this.lastRendered) return;
|
|
@@ -17871,7 +18095,7 @@ function makeToolActionCallback(chatId, channel, level) {
|
|
|
17871
18095
|
};
|
|
17872
18096
|
}
|
|
17873
18097
|
async function processFileSends2(chatId, channel, text) {
|
|
17874
|
-
const fileSendPattern = /\[SEND_FILE
|
|
18098
|
+
const fileSendPattern = /\[\s*SEND_FILE:\s*(.+?)\s*\]/g;
|
|
17875
18099
|
const filePaths = [];
|
|
17876
18100
|
for (const match of text.matchAll(fileSendPattern)) {
|
|
17877
18101
|
filePaths.push(match[1].trim());
|
|
@@ -17892,7 +18116,7 @@ async function processFileSends2(chatId, channel, text) {
|
|
|
17892
18116
|
return text.replace(fileSendPattern, "").trim();
|
|
17893
18117
|
}
|
|
17894
18118
|
async function processImageGenerations(chatId, channel, text) {
|
|
17895
|
-
const pattern = /\[GENERATE_IMAGE
|
|
18119
|
+
const pattern = /\[\s*GENERATE_IMAGE:\s*(.+?)\s*\]/g;
|
|
17896
18120
|
const prompts = [];
|
|
17897
18121
|
for (const match of text.matchAll(pattern)) {
|
|
17898
18122
|
prompts.push(match[1].trim());
|
|
@@ -17920,7 +18144,7 @@ async function processImageGenerations(chatId, channel, text) {
|
|
|
17920
18144
|
return text.replace(pattern, "").trim();
|
|
17921
18145
|
}
|
|
17922
18146
|
async function processReaction(chatId, channel, text, messageId) {
|
|
17923
|
-
const reactPatternGlobal = /\[REACT
|
|
18147
|
+
const reactPatternGlobal = /\[\s*REACT:\s*(.+?)\s*\]/g;
|
|
17924
18148
|
if (!reactPatternGlobal.test(text)) return text;
|
|
17925
18149
|
let reacted = false;
|
|
17926
18150
|
reactPatternGlobal.lastIndex = 0;
|
|
@@ -17934,7 +18158,7 @@ async function processReaction(chatId, channel, text, messageId) {
|
|
|
17934
18158
|
reacted = true;
|
|
17935
18159
|
}
|
|
17936
18160
|
}
|
|
17937
|
-
return text.replace(/\[REACT
|
|
18161
|
+
return text.replace(/\[\s*REACT:\s*(.+?)\s*\]/g, "").trim();
|
|
17938
18162
|
}
|
|
17939
18163
|
async function sendResponse(chatId, channel, text, messageId, replyToMessageId) {
|
|
17940
18164
|
text = await processReaction(chatId, channel, text, messageId);
|
|
@@ -17947,9 +18171,9 @@ async function sendResponse(chatId, channel, text, messageId, replyToMessageId)
|
|
|
17947
18171
|
}
|
|
17948
18172
|
}
|
|
17949
18173
|
let afterHistory = afterUpdates;
|
|
17950
|
-
const historySearchMatch = afterHistory.match(/\[HISTORY_SEARCH
|
|
18174
|
+
const historySearchMatch = afterHistory.match(/\[\s*HISTORY_SEARCH:\s*([^\]]+?)\s*\]/);
|
|
17951
18175
|
if (historySearchMatch) {
|
|
17952
|
-
afterHistory = afterHistory.replace(/\[HISTORY_SEARCH
|
|
18176
|
+
afterHistory = afterHistory.replace(/\[\s*HISTORY_SEARCH:\s*[^\]]+?\s*\]/g, "").trim();
|
|
17953
18177
|
const hsQuery = historySearchMatch[1].trim();
|
|
17954
18178
|
const hsResults = searchMessageLog(chatId, hsQuery, 10);
|
|
17955
18179
|
if (hsResults.length > 0) {
|
|
@@ -19846,15 +20070,20 @@ async function sendJobDetail(chatId, jobId, channel, messageId) {
|
|
|
19846
20070
|
\u26A0\uFE0F ${job.consecutiveFailures} consecutive failures` : "";
|
|
19847
20071
|
const runs = getJobRuns(jobId, 1);
|
|
19848
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);
|
|
19849
20075
|
const lines = [
|
|
19850
20076
|
`Job #${job.id}: ${job.title ?? job.description}`,
|
|
19851
20077
|
buildSectionHeader("", 22),
|
|
19852
20078
|
...job.title ? [`Task: ${job.description}`] : [],
|
|
19853
20079
|
`Runs: ${schedule2}${tz}`,
|
|
19854
|
-
|
|
20080
|
+
"",
|
|
19855
20081
|
`Last run: ${lastRun}${lastRunStatus ? ` (${lastRunStatus})` : ""}`,
|
|
19856
20082
|
`Next run: ${nextRun}`,
|
|
19857
|
-
`Status: ${status}${failures}
|
|
20083
|
+
`Status: ${status}${failures}`,
|
|
20084
|
+
"",
|
|
20085
|
+
`\u{1F3F7} ${backend2} \xB7 ${model2} \xB7 ${thinking2}`,
|
|
20086
|
+
`\u{1F464} ${accountLabel}`
|
|
19858
20087
|
];
|
|
19859
20088
|
const text = lines.join("\n");
|
|
19860
20089
|
if (typeof channel.sendKeyboard !== "function") {
|
|
@@ -20067,6 +20296,7 @@ var init_ui = __esm({
|
|
|
20067
20296
|
init_format_time();
|
|
20068
20297
|
init_cron();
|
|
20069
20298
|
init_humanize();
|
|
20299
|
+
init_wizard();
|
|
20070
20300
|
init_stt();
|
|
20071
20301
|
init_helpers();
|
|
20072
20302
|
ROTATION_MODE_LABELS = {
|
|
@@ -25933,7 +26163,7 @@ async function handleText(msg, channel) {
|
|
|
25933
26163
|
await channel.sendText(chatId, limitMsg, { parseMode: "plain" });
|
|
25934
26164
|
return;
|
|
25935
26165
|
}
|
|
25936
|
-
let intent =
|
|
26166
|
+
let intent = await classifyIntentAsync(text, chatId);
|
|
25937
26167
|
const cleanText = text.startsWith(">>") ? text.slice(2).trim() : text;
|
|
25938
26168
|
let bootstrapTier = intent === "chat" ? "chat" : void 0;
|
|
25939
26169
|
let maxTurns = void 0;
|
|
@@ -25977,9 +26207,10 @@ async function handleText(msg, channel) {
|
|
|
25977
26207
|
agentMode: effectiveAgentMode
|
|
25978
26208
|
});
|
|
25979
26209
|
if (response.text) {
|
|
25980
|
-
|
|
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);
|
|
25981
26212
|
if (typeof channel.sendKeyboard === "function") {
|
|
25982
|
-
await channel.sendKeyboard(chatId, `\u{1F50D} ${
|
|
26213
|
+
await channel.sendKeyboard(chatId, `\u{1F50D} ${revisedPlan}`, [
|
|
25983
26214
|
[
|
|
25984
26215
|
{ label: "\u2705 Approve", data: "exec:approve", style: "success" },
|
|
25985
26216
|
{ label: "\u274C Reject", data: "exec:reject", style: "danger" }
|
|
@@ -26115,7 +26346,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26115
26346
|
})) {
|
|
26116
26347
|
const planDirective = buildPlanningDirective();
|
|
26117
26348
|
let typingActive2 = true;
|
|
26118
|
-
const
|
|
26349
|
+
const typingLoop = async () => {
|
|
26119
26350
|
while (typingActive2) {
|
|
26120
26351
|
try {
|
|
26121
26352
|
await channel.sendTyping?.(chatId);
|
|
@@ -26124,7 +26355,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26124
26355
|
await new Promise((r) => setTimeout(r, 4e3));
|
|
26125
26356
|
}
|
|
26126
26357
|
};
|
|
26127
|
-
|
|
26358
|
+
typingLoop().catch(() => {
|
|
26128
26359
|
});
|
|
26129
26360
|
try {
|
|
26130
26361
|
const planResponse = await askAgent(chatId, cleanText || text, {
|
|
@@ -26165,18 +26396,23 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
|
|
|
26165
26396
|
}
|
|
26166
26397
|
return;
|
|
26167
26398
|
}
|
|
26168
|
-
|
|
26169
|
-
const
|
|
26170
|
-
|
|
26171
|
-
|
|
26172
|
-
|
|
26173
|
-
|
|
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));
|
|
26174
26411
|
}
|
|
26175
|
-
|
|
26176
|
-
|
|
26177
|
-
|
|
26178
|
-
|
|
26179
|
-
});
|
|
26412
|
+
};
|
|
26413
|
+
typingLoop().catch(() => {
|
|
26414
|
+
});
|
|
26415
|
+
}
|
|
26180
26416
|
try {
|
|
26181
26417
|
const tMode = settings.getMode();
|
|
26182
26418
|
const tVerbose = settings.getVerboseLevel();
|
|
@@ -26799,6 +27035,13 @@ async function runWithRetry(job, model2, runId, t0) {
|
|
|
26799
27035
|
const cronBackend = currentBackend;
|
|
26800
27036
|
setAllowPaidSlots(chatId, cronBackend);
|
|
26801
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
|
+
}
|
|
26802
27045
|
const response = await askAgent(chatId, job.description, {
|
|
26803
27046
|
model: currentModel,
|
|
26804
27047
|
backend: currentBackend,
|
|
@@ -26870,6 +27113,7 @@ var init_cron = __esm({
|
|
|
26870
27113
|
"src/scheduler/cron.ts"() {
|
|
26871
27114
|
"use strict";
|
|
26872
27115
|
init_store5();
|
|
27116
|
+
init_types();
|
|
26873
27117
|
init_backends();
|
|
26874
27118
|
init_agent();
|
|
26875
27119
|
init_log();
|
|
@@ -27560,6 +27804,115 @@ ${body.replace(/<[^>]*>/g, "").trim()}</code>
|
|
|
27560
27804
|
}
|
|
27561
27805
|
});
|
|
27562
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
|
+
|
|
27563
27916
|
// src/channels/telegram.ts
|
|
27564
27917
|
import { API_CONSTANTS, Bot, GrammyError as GrammyError2, InlineKeyboard, InputFile } from "grammy";
|
|
27565
27918
|
function isFastPathMessage(msg) {
|
|
@@ -27590,10 +27943,11 @@ var init_telegram2 = __esm({
|
|
|
27590
27943
|
"use strict";
|
|
27591
27944
|
init_telegram();
|
|
27592
27945
|
init_log();
|
|
27946
|
+
init_health3();
|
|
27593
27947
|
init_store5();
|
|
27594
27948
|
init_telegram_throttle();
|
|
27595
27949
|
FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
|
|
27596
|
-
TelegramChannel = class {
|
|
27950
|
+
TelegramChannel = class _TelegramChannel {
|
|
27597
27951
|
name = "telegram";
|
|
27598
27952
|
bot;
|
|
27599
27953
|
allowedChatIds;
|
|
@@ -27603,6 +27957,19 @@ var init_telegram2 = __esm({
|
|
|
27603
27957
|
// messageId → chatId
|
|
27604
27958
|
reactionHandlers = [];
|
|
27605
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
|
|
27606
27973
|
constructor() {
|
|
27607
27974
|
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
27608
27975
|
if (!token) {
|
|
@@ -27620,6 +27987,10 @@ var init_telegram2 = __esm({
|
|
|
27620
27987
|
this.bot = new Bot(token);
|
|
27621
27988
|
this.throttle = new TelegramThrottle();
|
|
27622
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
|
+
}
|
|
27623
27994
|
try {
|
|
27624
27995
|
await this.bot.api.sendMessage(
|
|
27625
27996
|
numericChatId(chatId),
|
|
@@ -27660,6 +28031,8 @@ var init_telegram2 = __esm({
|
|
|
27660
28031
|
{ command: "newchat", description: "Start a fresh conversation" },
|
|
27661
28032
|
{ command: "summarize", description: "Save session to memory (or 'all' for pre-restart)" },
|
|
27662
28033
|
{ command: "stop", description: "Cancel the current running task" },
|
|
28034
|
+
{ command: "debug", description: "Toggle session debug logging" },
|
|
28035
|
+
{ command: "imagine", description: "Generate an image from a prompt" },
|
|
27663
28036
|
// Backend & model
|
|
27664
28037
|
{ command: "backend", description: "Switch AI backend (Claude/Gemini/Codex/Cursor)" },
|
|
27665
28038
|
{ command: "claude", description: "Switch to Claude backend" },
|
|
@@ -27708,6 +28081,8 @@ var init_telegram2 = __esm({
|
|
|
27708
28081
|
{ command: "mcp", description: "List MCP servers across all backends" },
|
|
27709
28082
|
// Skills & profile
|
|
27710
28083
|
{ command: "skills", description: "List and invoke skills" },
|
|
28084
|
+
{ command: "extract_skill", description: "Extract a reusable skill from this session" },
|
|
28085
|
+
{ command: "skill_install", description: "Install a skill from GitHub" },
|
|
27711
28086
|
{ command: "voice", description: "Toggle voice responses" },
|
|
27712
28087
|
{ command: "voice_config", description: "Configure voice provider and voice" },
|
|
27713
28088
|
{ command: "response_style", description: "Set the AI response style (concise/normal/detailed)" },
|
|
@@ -27727,6 +28102,7 @@ var init_telegram2 = __esm({
|
|
|
27727
28102
|
{ command: "council", description: "Multi-model debate (select models, anonymous rounds)" }
|
|
27728
28103
|
]);
|
|
27729
28104
|
this.bot.on("message", async (ctx) => {
|
|
28105
|
+
this.lastUpdateAt = Date.now();
|
|
27730
28106
|
const chatId = ctx.chat.id.toString();
|
|
27731
28107
|
const senderId = ctx.from?.id?.toString() ?? "";
|
|
27732
28108
|
const authorized = this.isAuthorized(chatId) || this.isAuthorized(senderId);
|
|
@@ -27754,6 +28130,7 @@ var init_telegram2 = __esm({
|
|
|
27754
28130
|
});
|
|
27755
28131
|
});
|
|
27756
28132
|
this.bot.on("callback_query:data", (ctx) => {
|
|
28133
|
+
this.lastUpdateAt = Date.now();
|
|
27757
28134
|
const userId = ctx.from.id.toString();
|
|
27758
28135
|
const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
|
|
27759
28136
|
log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
|
|
@@ -27781,6 +28158,7 @@ var init_telegram2 = __esm({
|
|
|
27781
28158
|
});
|
|
27782
28159
|
});
|
|
27783
28160
|
this.bot.on("message_reaction", async (ctx) => {
|
|
28161
|
+
this.lastUpdateAt = Date.now();
|
|
27784
28162
|
const chatId = String(ctx.chat.id);
|
|
27785
28163
|
const messageId = ctx.messageReaction.message_id;
|
|
27786
28164
|
if (!this.agentMessageIds.has(messageId)) return;
|
|
@@ -27797,6 +28175,7 @@ var init_telegram2 = __esm({
|
|
|
27797
28175
|
}
|
|
27798
28176
|
});
|
|
27799
28177
|
this.bot.on("inline_query", (ctx) => {
|
|
28178
|
+
this.lastUpdateAt = Date.now();
|
|
27800
28179
|
if (!this.isAuthorized(ctx.from.id.toString())) return;
|
|
27801
28180
|
this.handleInlineQuery(ctx).catch((err) => {
|
|
27802
28181
|
error("[telegram] Inline query error:", err);
|
|
@@ -27810,25 +28189,64 @@ var init_telegram2 = __esm({
|
|
|
27810
28189
|
error("[telegram] Unhandled error:", err);
|
|
27811
28190
|
}
|
|
27812
28191
|
});
|
|
27813
|
-
this.
|
|
28192
|
+
this.pollingExpected = true;
|
|
28193
|
+
this.lastUpdateAt = Date.now();
|
|
28194
|
+
const pollingPromise = this.bot.start({
|
|
27814
28195
|
allowed_updates: [...API_CONSTANTS.ALL_UPDATE_TYPES],
|
|
27815
28196
|
onStart: () => log("[telegram] Polling for messages...")
|
|
27816
|
-
}).catch((err) => {
|
|
27817
|
-
error("[telegram] Fatal: bot.start() failed:", err);
|
|
27818
|
-
error("[telegram] Check TELEGRAM_BOT_TOKEN in ~/.cc-claw/.env \u2014 it may be invalid or revoked.");
|
|
27819
|
-
error("[telegram] To regenerate: message @BotFather on Telegram, use /revoke, then 'cc-claw setup'");
|
|
27820
|
-
process.exit(1);
|
|
27821
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);
|
|
27822
28225
|
}
|
|
27823
28226
|
async stop() {
|
|
27824
|
-
|
|
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
|
+
}
|
|
27825
28240
|
}
|
|
27826
28241
|
async sendTyping(chatId, threadId) {
|
|
27827
|
-
if (this.throttle.isPaused()) return;
|
|
27828
28242
|
try {
|
|
27829
|
-
await this.
|
|
27830
|
-
|
|
27831
|
-
|
|
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
|
+
);
|
|
27832
28250
|
} catch {
|
|
27833
28251
|
}
|
|
27834
28252
|
}
|
|
@@ -27933,7 +28351,12 @@ var init_telegram2 = __esm({
|
|
|
27933
28351
|
);
|
|
27934
28352
|
return true;
|
|
27935
28353
|
} catch (err) {
|
|
27936
|
-
|
|
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);
|
|
27937
28360
|
try {
|
|
27938
28361
|
await this.throttle.send(
|
|
27939
28362
|
chatId,
|
|
@@ -28071,9 +28494,13 @@ var init_telegram2 = __esm({
|
|
|
28071
28494
|
}
|
|
28072
28495
|
async reactToMessage(chatId, messageId, emoji) {
|
|
28073
28496
|
try {
|
|
28074
|
-
await this.
|
|
28075
|
-
|
|
28076
|
-
|
|
28497
|
+
await this.throttle.tryBestEffort(
|
|
28498
|
+
chatId,
|
|
28499
|
+
"reaction",
|
|
28500
|
+
() => this.bot.api.setMessageReaction(numericChatId(chatId), parseInt(messageId), [
|
|
28501
|
+
{ type: "emoji", emoji }
|
|
28502
|
+
])
|
|
28503
|
+
);
|
|
28077
28504
|
} catch (err) {
|
|
28078
28505
|
log(`[telegram] reactToMessage failed (chat=${chatId} msg=${messageId}): ${err}`);
|
|
28079
28506
|
}
|
|
@@ -28491,115 +28918,6 @@ var init_bootstrap2 = __esm({
|
|
|
28491
28918
|
}
|
|
28492
28919
|
});
|
|
28493
28920
|
|
|
28494
|
-
// src/channels/health.ts
|
|
28495
|
-
function trackChannel(channel) {
|
|
28496
|
-
healthState.set(channel.name, {
|
|
28497
|
-
status: "healthy",
|
|
28498
|
-
startedAt: Date.now(),
|
|
28499
|
-
lastHealthyAt: Date.now(),
|
|
28500
|
-
lastErrorAt: null,
|
|
28501
|
-
lastError: null,
|
|
28502
|
-
reconnectAttempts: 0
|
|
28503
|
-
});
|
|
28504
|
-
}
|
|
28505
|
-
function markChannelHealthy(name) {
|
|
28506
|
-
const state = healthState.get(name);
|
|
28507
|
-
if (state) {
|
|
28508
|
-
state.status = "healthy";
|
|
28509
|
-
state.lastHealthyAt = Date.now();
|
|
28510
|
-
state.reconnectAttempts = 0;
|
|
28511
|
-
}
|
|
28512
|
-
}
|
|
28513
|
-
function markChannelDown(name, error3) {
|
|
28514
|
-
const state = healthState.get(name);
|
|
28515
|
-
if (state) {
|
|
28516
|
-
state.status = "down";
|
|
28517
|
-
state.lastErrorAt = Date.now();
|
|
28518
|
-
state.lastError = error3;
|
|
28519
|
-
warn(`[channel-health] ${name} marked as down: ${error3}`);
|
|
28520
|
-
}
|
|
28521
|
-
}
|
|
28522
|
-
function getChannelHealth(name) {
|
|
28523
|
-
const state = healthState.get(name);
|
|
28524
|
-
if (!state) return null;
|
|
28525
|
-
return {
|
|
28526
|
-
name,
|
|
28527
|
-
status: state.status,
|
|
28528
|
-
lastHealthyAt: state.lastHealthyAt,
|
|
28529
|
-
lastErrorAt: state.lastErrorAt,
|
|
28530
|
-
lastError: state.lastError,
|
|
28531
|
-
reconnectAttempts: state.reconnectAttempts,
|
|
28532
|
-
uptimeMs: state.status === "healthy" ? Date.now() - state.startedAt : 0
|
|
28533
|
-
};
|
|
28534
|
-
}
|
|
28535
|
-
async function attemptReconnect(channel, handler) {
|
|
28536
|
-
const state = healthState.get(channel.name);
|
|
28537
|
-
if (!state) return false;
|
|
28538
|
-
if (reconnecting.has(channel.name)) {
|
|
28539
|
-
log(`[channel-health] ${channel.name}: reconnect already in progress, skipping`);
|
|
28540
|
-
return false;
|
|
28541
|
-
}
|
|
28542
|
-
if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
28543
|
-
error(`[channel-health] ${channel.name}: max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached`);
|
|
28544
|
-
return false;
|
|
28545
|
-
}
|
|
28546
|
-
reconnecting.add(channel.name);
|
|
28547
|
-
state.reconnectAttempts++;
|
|
28548
|
-
const backoffMs = RECONNECT_BASE_MS * Math.pow(2, state.reconnectAttempts - 1);
|
|
28549
|
-
log(`[channel-health] ${channel.name}: reconnect attempt ${state.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS} in ${backoffMs}ms`);
|
|
28550
|
-
await new Promise((r) => setTimeout(r, backoffMs));
|
|
28551
|
-
try {
|
|
28552
|
-
await channel.stop().catch(() => {
|
|
28553
|
-
});
|
|
28554
|
-
await channel.start(handler);
|
|
28555
|
-
markChannelHealthy(channel.name);
|
|
28556
|
-
log(`[channel-health] ${channel.name}: reconnected successfully`);
|
|
28557
|
-
return true;
|
|
28558
|
-
} catch (err) {
|
|
28559
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
28560
|
-
markChannelDown(channel.name, msg);
|
|
28561
|
-
return false;
|
|
28562
|
-
} finally {
|
|
28563
|
-
reconnecting.delete(channel.name);
|
|
28564
|
-
}
|
|
28565
|
-
}
|
|
28566
|
-
function startHealthMonitor3(channels, handler) {
|
|
28567
|
-
registeredHandler = handler;
|
|
28568
|
-
for (const ch of channels) {
|
|
28569
|
-
trackChannel(ch);
|
|
28570
|
-
}
|
|
28571
|
-
healthInterval = setInterval(() => {
|
|
28572
|
-
for (const ch of channels) {
|
|
28573
|
-
const health = getChannelHealth(ch.name);
|
|
28574
|
-
if (health?.status === "down" && registeredHandler) {
|
|
28575
|
-
attemptReconnect(ch, registeredHandler).catch(() => {
|
|
28576
|
-
});
|
|
28577
|
-
}
|
|
28578
|
-
}
|
|
28579
|
-
}, HEALTH_CHECK_INTERVAL_MS);
|
|
28580
|
-
log(`[channel-health] Monitoring ${channels.length} channel(s)`);
|
|
28581
|
-
}
|
|
28582
|
-
function stopHealthMonitor3() {
|
|
28583
|
-
if (healthInterval) {
|
|
28584
|
-
clearInterval(healthInterval);
|
|
28585
|
-
healthInterval = null;
|
|
28586
|
-
}
|
|
28587
|
-
}
|
|
28588
|
-
var healthState, MAX_RECONNECT_ATTEMPTS, RECONNECT_BASE_MS, HEALTH_CHECK_INTERVAL_MS, healthInterval, registeredHandler, reconnecting;
|
|
28589
|
-
var init_health3 = __esm({
|
|
28590
|
-
"src/channels/health.ts"() {
|
|
28591
|
-
"use strict";
|
|
28592
|
-
init_log();
|
|
28593
|
-
healthState = /* @__PURE__ */ new Map();
|
|
28594
|
-
MAX_RECONNECT_ATTEMPTS = 5;
|
|
28595
|
-
RECONNECT_BASE_MS = 5e3;
|
|
28596
|
-
HEALTH_CHECK_INTERVAL_MS = 15e3;
|
|
28597
|
-
healthInterval = null;
|
|
28598
|
-
registeredHandler = null;
|
|
28599
|
-
reconnecting = /* @__PURE__ */ new Set();
|
|
28600
|
-
}
|
|
28601
|
-
});
|
|
28602
|
-
|
|
28603
28921
|
// src/cli/commands/ai-skill.ts
|
|
28604
28922
|
var ai_skill_exports = {};
|
|
28605
28923
|
__export(ai_skill_exports, {
|
package/package.json
CHANGED