cc-claw 0.20.10 → 0.20.12
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 +895 -646
- 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.12" : (() => {
|
|
37
37
|
try {
|
|
38
38
|
return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
39
39
|
} catch {
|
|
@@ -2721,15 +2721,16 @@ function toggleShowThinkingUi(chatId) {
|
|
|
2721
2721
|
function getSkillSuggestionsEnabled(chatId) {
|
|
2722
2722
|
const row = getDb().prepare(
|
|
2723
2723
|
"SELECT value FROM chat_skill_suggestions WHERE chat_id = ?"
|
|
2724
|
-
).get(
|
|
2724
|
+
).get(SKILL_SUGGESTIONS_GLOBAL_KEY);
|
|
2725
2725
|
return (row?.value ?? 1) === 1;
|
|
2726
2726
|
}
|
|
2727
|
-
function setSkillSuggestionsEnabled(
|
|
2727
|
+
function setSkillSuggestionsEnabled(chatIdOrEnabled, enabled) {
|
|
2728
|
+
const value = typeof chatIdOrEnabled === "boolean" ? chatIdOrEnabled : enabled;
|
|
2728
2729
|
getDb().prepare(`
|
|
2729
2730
|
INSERT INTO chat_skill_suggestions (chat_id, value)
|
|
2730
2731
|
VALUES (?, ?)
|
|
2731
2732
|
ON CONFLICT(chat_id) DO UPDATE SET value = ?
|
|
2732
|
-
`).run(
|
|
2733
|
+
`).run(SKILL_SUGGESTIONS_GLOBAL_KEY, value ? 1 : 0, value ? 1 : 0);
|
|
2733
2734
|
}
|
|
2734
2735
|
function getMode(chatId) {
|
|
2735
2736
|
const row = getDb().prepare(
|
|
@@ -2933,12 +2934,13 @@ function clearChatPaidSlots(chatId) {
|
|
|
2933
2934
|
function clearAllPaidSlots() {
|
|
2934
2935
|
getDb().prepare("DELETE FROM chat_allow_paid_slots").run();
|
|
2935
2936
|
}
|
|
2936
|
-
var pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS, GLOBAL_SUMMARIZER_SENTINEL, ChatSettingsSnapshot;
|
|
2937
|
+
var SKILL_SUGGESTIONS_GLOBAL_KEY, pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS, GLOBAL_SUMMARIZER_SENTINEL, ChatSettingsSnapshot;
|
|
2937
2938
|
var init_chat_settings = __esm({
|
|
2938
2939
|
"src/memory/chat-settings.ts"() {
|
|
2939
2940
|
"use strict";
|
|
2940
2941
|
init_store5();
|
|
2941
2942
|
init_sessions();
|
|
2943
|
+
SKILL_SUGGESTIONS_GLOBAL_KEY = "__global__";
|
|
2942
2944
|
pendingEscalations = /* @__PURE__ */ new Map();
|
|
2943
2945
|
ESCALATION_TTL_MS = 5 * 60 * 1e3;
|
|
2944
2946
|
ALL_TOOLS = ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch", "Agent", "AskUserQuestion"];
|
|
@@ -3293,6 +3295,24 @@ var init_summaries = __esm({
|
|
|
3293
3295
|
});
|
|
3294
3296
|
|
|
3295
3297
|
// src/memory/jobs.ts
|
|
3298
|
+
var jobs_exports = {};
|
|
3299
|
+
__export(jobs_exports, {
|
|
3300
|
+
cancelJobById: () => cancelJobById,
|
|
3301
|
+
completeJobRun: () => completeJobRun,
|
|
3302
|
+
getActiveJobs: () => getActiveJobs,
|
|
3303
|
+
getAllJobs: () => getAllJobs,
|
|
3304
|
+
getJobById: () => getJobById,
|
|
3305
|
+
getJobRuns: () => getJobRuns,
|
|
3306
|
+
incrementJobFailures: () => incrementJobFailures,
|
|
3307
|
+
insertJob: () => insertJob,
|
|
3308
|
+
insertJobRun: () => insertJobRun,
|
|
3309
|
+
pruneJobRuns: () => pruneJobRuns,
|
|
3310
|
+
resetJobFailures: () => resetJobFailures,
|
|
3311
|
+
updateJob: () => updateJob,
|
|
3312
|
+
updateJobEnabled: () => updateJobEnabled,
|
|
3313
|
+
updateJobLastRun: () => updateJobLastRun,
|
|
3314
|
+
updateJobNextRun: () => updateJobNextRun
|
|
3315
|
+
});
|
|
3296
3316
|
function insertJob(params) {
|
|
3297
3317
|
const db3 = getDb();
|
|
3298
3318
|
const result = db3.prepare(`
|
|
@@ -5892,6 +5912,10 @@ async function routeModelForTask(task, preferredModel) {
|
|
|
5892
5912
|
if (!server) return void 0;
|
|
5893
5913
|
return { model: best.model, server, reason: best.reason };
|
|
5894
5914
|
}
|
|
5915
|
+
function getEligibleModelsForTask(task) {
|
|
5916
|
+
const available = getAvailableModels();
|
|
5917
|
+
return filterByTask(available, task).map((m) => m.name);
|
|
5918
|
+
}
|
|
5895
5919
|
function routeModelForTaskSync(task, preferredModel) {
|
|
5896
5920
|
if (preferredModel) {
|
|
5897
5921
|
const model2 = getModelByName(preferredModel);
|
|
@@ -6128,6 +6152,7 @@ var service_exports = {};
|
|
|
6128
6152
|
__export(service_exports, {
|
|
6129
6153
|
addServer: () => addServer2,
|
|
6130
6154
|
discoverModels: () => discoverModels,
|
|
6155
|
+
getEligibleModelsForTask: () => getEligibleModelsForTask,
|
|
6131
6156
|
getLoadedModels: () => getLoadedModels,
|
|
6132
6157
|
getModelForTask: () => getModelForTask,
|
|
6133
6158
|
getModelForTaskAsync: () => getModelForTaskAsync,
|
|
@@ -6199,8 +6224,8 @@ async function healthCheck(serverName) {
|
|
|
6199
6224
|
}
|
|
6200
6225
|
if (needsCatalogRefresh) {
|
|
6201
6226
|
try {
|
|
6202
|
-
const { getAdapter:
|
|
6203
|
-
const adapter =
|
|
6227
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
6228
|
+
const adapter = getAdapter4("ollama");
|
|
6204
6229
|
if ("refreshModelCatalog" in adapter) {
|
|
6205
6230
|
adapter.refreshModelCatalog();
|
|
6206
6231
|
}
|
|
@@ -9580,8 +9605,8 @@ async function spawnSubAgent(chatId, opts) {
|
|
|
9580
9605
|
if (!runner) throw new Error(`Unknown runner: ${opts.runner}`);
|
|
9581
9606
|
if (opts.model) {
|
|
9582
9607
|
try {
|
|
9583
|
-
const { getAdapter:
|
|
9584
|
-
const adapter =
|
|
9608
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
9609
|
+
const adapter = getAdapter4(opts.runner);
|
|
9585
9610
|
if (adapter.availableModels && !adapter.availableModels[opts.model]) {
|
|
9586
9611
|
const validModels = Object.keys(adapter.availableModels).join(", ");
|
|
9587
9612
|
throw new Error(`Unknown model "${opts.model}" for ${opts.runner}. Available: ${validModels}`);
|
|
@@ -9962,8 +9987,8 @@ async function handleAgentComplete(agentId, chatId, resultText, usage2, mcpsAdde
|
|
|
9962
9987
|
const runner = getRunner(agent.runnerId);
|
|
9963
9988
|
if (runner) {
|
|
9964
9989
|
try {
|
|
9965
|
-
const { getAdapter:
|
|
9966
|
-
const adapter =
|
|
9990
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
9991
|
+
const adapter = getAdapter4(agent.runnerId);
|
|
9967
9992
|
const model2 = agent.model ?? adapter.defaultModel;
|
|
9968
9993
|
const pricing = adapter.pricing[model2];
|
|
9969
9994
|
if (pricing) {
|
|
@@ -10712,7 +10737,7 @@ var init_chat = __esm({
|
|
|
10712
10737
|
}
|
|
10713
10738
|
const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
|
|
10714
10739
|
const { getMode: getMode3, getCwd: getCwd3, getModel: getModel3, addUsage: addUsage3, getBackend: getBackend3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
10715
|
-
const { getAdapterForChat:
|
|
10740
|
+
const { getAdapterForChat: getAdapterForChat2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
10716
10741
|
const chatId = body.chatId;
|
|
10717
10742
|
const backend2 = body.backend;
|
|
10718
10743
|
const PERM_LEVEL = { plan: 0, safe: 1, yolo: 2 };
|
|
@@ -10722,7 +10747,7 @@ var init_chat = __esm({
|
|
|
10722
10747
|
const cwd = body.cwd ?? getCwd3(chatId);
|
|
10723
10748
|
const model2 = body.model ?? getModel3(chatId) ?? (() => {
|
|
10724
10749
|
try {
|
|
10725
|
-
return
|
|
10750
|
+
return getAdapterForChat2(chatId).defaultModel;
|
|
10726
10751
|
} catch {
|
|
10727
10752
|
return void 0;
|
|
10728
10753
|
}
|
|
@@ -11358,8 +11383,8 @@ var init_config = __esm({
|
|
|
11358
11383
|
handleHeartbeatSet = async (req, res) => {
|
|
11359
11384
|
try {
|
|
11360
11385
|
const body = JSON.parse(await readBody(req));
|
|
11361
|
-
const { setHeartbeatConfig:
|
|
11362
|
-
|
|
11386
|
+
const { setHeartbeatConfig: setHeartbeatConfig4 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
11387
|
+
setHeartbeatConfig4(body.chatId, body);
|
|
11363
11388
|
jsonResponse(res, { success: true });
|
|
11364
11389
|
} catch (err) {
|
|
11365
11390
|
jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
@@ -13292,9 +13317,10 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
13292
13317
|
const env = opts?.envOverride ? thinkingConfig?.envOverrides ? { ...opts.envOverride, ...thinkingConfig.envOverrides } : opts.envOverride : adapter.getEnv(thinkingConfig?.envOverrides);
|
|
13293
13318
|
const finalArgs = thinkingConfig?.extraArgs ? [...config2.args, ...thinkingConfig.extraArgs] : config2.args;
|
|
13294
13319
|
log(`[agent:spawn] backend=${adapter.id} exe=${config2.executable} model=${model2} timeout=${effectiveTimeout / 1e3}s cwd=${config2.cwd ?? "(inherited)"}`);
|
|
13320
|
+
const stdinMode = adapter.id === "codex" ? "pipe" : "ignore";
|
|
13295
13321
|
const proc = spawn6(config2.executable, finalArgs, {
|
|
13296
13322
|
env,
|
|
13297
|
-
stdio: [
|
|
13323
|
+
stdio: [stdinMode, "pipe", "pipe"],
|
|
13298
13324
|
detached: true,
|
|
13299
13325
|
...config2.cwd ? { cwd: config2.cwd } : {}
|
|
13300
13326
|
});
|
|
@@ -14561,9 +14587,636 @@ var init_propose = __esm({
|
|
|
14561
14587
|
}
|
|
14562
14588
|
});
|
|
14563
14589
|
|
|
14564
|
-
// src/
|
|
14565
|
-
|
|
14590
|
+
// src/channels/telegram-throttle.ts
|
|
14591
|
+
var telegram_throttle_exports = {};
|
|
14592
|
+
__export(telegram_throttle_exports, {
|
|
14593
|
+
TelegramThrottle: () => TelegramThrottle,
|
|
14594
|
+
getThrottleState: () => getThrottleState
|
|
14595
|
+
});
|
|
14596
|
+
import { GrammyError } from "grammy";
|
|
14597
|
+
function getThrottleState() {
|
|
14598
|
+
if (!_activeThrottle) return null;
|
|
14599
|
+
return _activeThrottle.getState();
|
|
14600
|
+
}
|
|
14601
|
+
function is429(err) {
|
|
14602
|
+
return err instanceof GrammyError && err.error_code === 429;
|
|
14603
|
+
}
|
|
14604
|
+
function sleep(ms) {
|
|
14605
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
14606
|
+
}
|
|
14607
|
+
var PER_CHAT_INTERVAL_MS, GLOBAL_INTERVAL_MS, MAX_RETRIES2, RETRY_DELAY_MS, MAX_QUEUE_SIZE, MAX_TOTAL_PAUSE_MS, _activeThrottle, TelegramThrottle;
|
|
14608
|
+
var init_telegram_throttle = __esm({
|
|
14609
|
+
"src/channels/telegram-throttle.ts"() {
|
|
14610
|
+
"use strict";
|
|
14611
|
+
init_log();
|
|
14612
|
+
PER_CHAT_INTERVAL_MS = 350;
|
|
14613
|
+
GLOBAL_INTERVAL_MS = 50;
|
|
14614
|
+
MAX_RETRIES2 = 2;
|
|
14615
|
+
RETRY_DELAY_MS = 1e3;
|
|
14616
|
+
MAX_QUEUE_SIZE = 100;
|
|
14617
|
+
MAX_TOTAL_PAUSE_MS = 30 * 60 * 1e3;
|
|
14618
|
+
_activeThrottle = null;
|
|
14619
|
+
TelegramThrottle = class {
|
|
14620
|
+
queue = [];
|
|
14621
|
+
processing = false;
|
|
14622
|
+
lastSendPerChat = /* @__PURE__ */ new Map();
|
|
14623
|
+
lastGlobalSend = 0;
|
|
14624
|
+
// Pause state
|
|
14625
|
+
pausedUntil = 0;
|
|
14626
|
+
pauseStartedAt = 0;
|
|
14627
|
+
chatsPendingNotification = /* @__PURE__ */ new Set();
|
|
14628
|
+
resumeNotifier;
|
|
14629
|
+
constructor() {
|
|
14630
|
+
_activeThrottle = this;
|
|
14631
|
+
}
|
|
14632
|
+
/**
|
|
14633
|
+
* Register a callback that fires when the throttle resumes after a pause.
|
|
14634
|
+
* The callback should send a message directly via bot.api (NOT through the throttle).
|
|
14635
|
+
*/
|
|
14636
|
+
setResumeNotifier(fn) {
|
|
14637
|
+
this.resumeNotifier = fn;
|
|
14638
|
+
}
|
|
14639
|
+
/** Enqueue a Telegram API call with automatic pacing and 429 handling. */
|
|
14640
|
+
async send(chatId, label2, fn) {
|
|
14641
|
+
return new Promise((resolve, reject) => {
|
|
14642
|
+
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
14643
|
+
const dropped = this.queue.shift();
|
|
14644
|
+
if (dropped) {
|
|
14645
|
+
warn(`[throttle] Queue full (${MAX_QUEUE_SIZE}), dropping oldest: ${dropped.label}`);
|
|
14646
|
+
dropped.reject(new Error("Dropped from send queue (overflow)"));
|
|
14647
|
+
}
|
|
14648
|
+
}
|
|
14649
|
+
this.queue.push({ chatId, label: label2, fn, resolve, reject });
|
|
14650
|
+
this.drain();
|
|
14651
|
+
});
|
|
14652
|
+
}
|
|
14653
|
+
/** Check whether the throttle is currently paused (rate-limited). */
|
|
14654
|
+
isPaused() {
|
|
14655
|
+
return Date.now() < this.pausedUntil;
|
|
14656
|
+
}
|
|
14657
|
+
/** Get structured state for diagnostics / health checks. */
|
|
14658
|
+
getState() {
|
|
14659
|
+
const now = Date.now();
|
|
14660
|
+
const paused = now < this.pausedUntil;
|
|
14661
|
+
return {
|
|
14662
|
+
isPaused: paused,
|
|
14663
|
+
queueDepth: this.queue.length,
|
|
14664
|
+
pausedUntilMs: this.pausedUntil,
|
|
14665
|
+
pauseRemainingSec: paused ? Math.ceil((this.pausedUntil - now) / 1e3) : 0
|
|
14666
|
+
};
|
|
14667
|
+
}
|
|
14668
|
+
// ── Queue processor ─────────────────────────────────────────────────
|
|
14669
|
+
async drain() {
|
|
14670
|
+
if (this.processing) return;
|
|
14671
|
+
this.processing = true;
|
|
14672
|
+
try {
|
|
14673
|
+
while (this.queue.length > 0) {
|
|
14674
|
+
while (this.isPaused()) {
|
|
14675
|
+
if (this.pauseStartedAt > 0 && Date.now() - this.pauseStartedAt > MAX_TOTAL_PAUSE_MS) {
|
|
14676
|
+
warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items`);
|
|
14677
|
+
this.flushQueueWithError("Telegram rate limit exceeded max wait time");
|
|
14678
|
+
this.pausedUntil = 0;
|
|
14679
|
+
this.pauseStartedAt = 0;
|
|
14680
|
+
this.chatsPendingNotification.clear();
|
|
14681
|
+
break;
|
|
14682
|
+
}
|
|
14683
|
+
const waitMs = Math.min(this.pausedUntil - Date.now(), 5e3);
|
|
14684
|
+
if (waitMs > 0) await sleep(waitMs);
|
|
14685
|
+
}
|
|
14686
|
+
if (this.queue.length === 0) break;
|
|
14687
|
+
if (this.chatsPendingNotification.size > 0) {
|
|
14688
|
+
await this.sendResumeNotifications();
|
|
14689
|
+
}
|
|
14690
|
+
const item = this.queue[0];
|
|
14691
|
+
const lastChat = this.lastSendPerChat.get(item.chatId) ?? 0;
|
|
14692
|
+
const chatWait = PER_CHAT_INTERVAL_MS - (Date.now() - lastChat);
|
|
14693
|
+
if (chatWait > 0) await sleep(chatWait);
|
|
14694
|
+
const globalWait = GLOBAL_INTERVAL_MS - (Date.now() - this.lastGlobalSend);
|
|
14695
|
+
if (globalWait > 0) await sleep(globalWait);
|
|
14696
|
+
this.queue.shift();
|
|
14697
|
+
try {
|
|
14698
|
+
const result = await this.execWithRetry(item.label, item.fn);
|
|
14699
|
+
this.recordSend(item.chatId);
|
|
14700
|
+
this.pauseStartedAt = 0;
|
|
14701
|
+
item.resolve(result);
|
|
14702
|
+
} catch (err) {
|
|
14703
|
+
if (is429(err)) {
|
|
14704
|
+
const retrySec = err.parameters?.retry_after ?? 10;
|
|
14705
|
+
this.enterPause(retrySec, item);
|
|
14706
|
+
continue;
|
|
14707
|
+
}
|
|
14708
|
+
item.reject(err);
|
|
14709
|
+
}
|
|
14710
|
+
}
|
|
14711
|
+
} finally {
|
|
14712
|
+
this.processing = false;
|
|
14713
|
+
}
|
|
14714
|
+
}
|
|
14715
|
+
// ── Retry logic (non-429 errors only) ───────────────────────────────
|
|
14716
|
+
async execWithRetry(label2, fn) {
|
|
14717
|
+
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
14718
|
+
try {
|
|
14719
|
+
return await fn();
|
|
14720
|
+
} catch (err) {
|
|
14721
|
+
if (is429(err)) throw err;
|
|
14722
|
+
if (attempt < MAX_RETRIES2 && err instanceof GrammyError) {
|
|
14723
|
+
warn(`[throttle] ${label2} attempt ${attempt + 1}/${MAX_RETRIES2} failed (${err.error_code}), retrying`);
|
|
14724
|
+
await sleep(RETRY_DELAY_MS);
|
|
14725
|
+
continue;
|
|
14726
|
+
}
|
|
14727
|
+
throw err;
|
|
14728
|
+
}
|
|
14729
|
+
}
|
|
14730
|
+
throw new Error("unreachable");
|
|
14731
|
+
}
|
|
14732
|
+
// ── Pause management ────────────────────────────────────────────────
|
|
14733
|
+
enterPause(retrySec, failedItem) {
|
|
14734
|
+
this.queue.unshift(failedItem);
|
|
14735
|
+
this.pausedUntil = Date.now() + retrySec * 1e3;
|
|
14736
|
+
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
14737
|
+
for (const qi of this.queue) {
|
|
14738
|
+
this.chatsPendingNotification.add(qi.chatId);
|
|
14739
|
+
}
|
|
14740
|
+
warn(`[throttle] 429 \u2014 pausing ALL sends for ${retrySec}s (${this.queue.length} items queued)`);
|
|
14741
|
+
}
|
|
14742
|
+
async sendResumeNotifications() {
|
|
14743
|
+
const chats2 = new Set(this.chatsPendingNotification);
|
|
14744
|
+
this.chatsPendingNotification.clear();
|
|
14745
|
+
if (!this.resumeNotifier) return;
|
|
14746
|
+
const pausedSec = this.pauseStartedAt > 0 ? Math.round((Date.now() - this.pauseStartedAt) / 1e3) : 0;
|
|
14747
|
+
for (const chatId of chats2) {
|
|
14748
|
+
const queuedForChat = this.queue.filter((q) => q.chatId === chatId).length;
|
|
14749
|
+
if (queuedForChat === 0) continue;
|
|
14750
|
+
try {
|
|
14751
|
+
await this.resumeNotifier(chatId, pausedSec, queuedForChat);
|
|
14752
|
+
this.recordSend(chatId);
|
|
14753
|
+
} catch (err) {
|
|
14754
|
+
if (is429(err)) {
|
|
14755
|
+
const retrySec = err.parameters?.retry_after ?? 10;
|
|
14756
|
+
this.pausedUntil = Date.now() + retrySec * 1e3;
|
|
14757
|
+
warn(`[throttle] Resume notification hit 429, re-pausing for ${retrySec}s (skipping further notifications)`);
|
|
14758
|
+
return;
|
|
14759
|
+
}
|
|
14760
|
+
warn(`[throttle] Resume notification failed for chat ${chatId}: ${err}`);
|
|
14761
|
+
}
|
|
14762
|
+
}
|
|
14763
|
+
this.pauseStartedAt = 0;
|
|
14764
|
+
}
|
|
14765
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
14766
|
+
recordSend(chatId) {
|
|
14767
|
+
const now = Date.now();
|
|
14768
|
+
this.lastSendPerChat.set(chatId, now);
|
|
14769
|
+
this.lastGlobalSend = now;
|
|
14770
|
+
}
|
|
14771
|
+
flushQueueWithError(message) {
|
|
14772
|
+
while (this.queue.length > 0) {
|
|
14773
|
+
const item = this.queue.shift();
|
|
14774
|
+
item.reject(new Error(message));
|
|
14775
|
+
}
|
|
14776
|
+
}
|
|
14777
|
+
};
|
|
14778
|
+
}
|
|
14779
|
+
});
|
|
14780
|
+
|
|
14781
|
+
// src/health/checks.ts
|
|
14782
|
+
import { existsSync as existsSync14, statSync as statSync6, readFileSync as readFileSync8 } from "fs";
|
|
14783
|
+
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
14784
|
+
function getRecentErrors() {
|
|
14785
|
+
if (!existsSync14(ERROR_LOG_PATH)) return null;
|
|
14786
|
+
const logContent = readFileSync8(ERROR_LOG_PATH, "utf-8");
|
|
14787
|
+
const allLines = logContent.split("\n").filter(Boolean).slice(-500);
|
|
14788
|
+
const last24h = Date.now() - 864e5;
|
|
14789
|
+
const lines = allLines.filter((line) => {
|
|
14790
|
+
const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:+-]+)/);
|
|
14791
|
+
if (match) return (/* @__PURE__ */ new Date(`${match[1]}T${match[2]}`)).getTime() > last24h;
|
|
14792
|
+
return false;
|
|
14793
|
+
});
|
|
14794
|
+
if (lines.length === 0) return null;
|
|
14795
|
+
const classified = { rate429: [], contentSilence: [], spawnTimeout: [], other: [], total: 0 };
|
|
14796
|
+
for (const line of lines) {
|
|
14797
|
+
if (/429|rate.?limit/i.test(line)) classified.rate429.push(line);
|
|
14798
|
+
else if (/content silence/i.test(line)) classified.contentSilence.push(line);
|
|
14799
|
+
else if (/spawn timeout|timeout after \d+s/i.test(line)) classified.spawnTimeout.push(line);
|
|
14800
|
+
else classified.other.push(line);
|
|
14801
|
+
}
|
|
14802
|
+
classified.total = lines.length;
|
|
14803
|
+
return classified;
|
|
14804
|
+
}
|
|
14805
|
+
function checkDatabase() {
|
|
14806
|
+
const checks = [];
|
|
14807
|
+
if (existsSync14(DB_PATH)) {
|
|
14808
|
+
const size = statSync6(DB_PATH).size;
|
|
14809
|
+
checks.push({ name: "Database", status: "ok", message: `${(size / 1024).toFixed(0)}KB` });
|
|
14810
|
+
} else {
|
|
14811
|
+
checks.push({ name: "Database", status: "error", message: "Not found", fix: "cc-claw setup" });
|
|
14812
|
+
}
|
|
14813
|
+
return checks;
|
|
14814
|
+
}
|
|
14815
|
+
function checkDiskSpace() {
|
|
14816
|
+
try {
|
|
14817
|
+
const dfOutput = execSync3("df -k " + DATA_PATH, { encoding: "utf-8" });
|
|
14818
|
+
const lines = dfOutput.trim().split("\n");
|
|
14819
|
+
if (lines.length >= 2) {
|
|
14820
|
+
const parts = lines[1].split(/\s+/);
|
|
14821
|
+
const availKB = parseInt(parts[3], 10);
|
|
14822
|
+
if (availKB < 1e5) {
|
|
14823
|
+
return { name: "Disk space", status: "warning", message: `${(availKB / 1024).toFixed(0)}MB available` };
|
|
14824
|
+
}
|
|
14825
|
+
return { name: "Disk space", status: "ok", message: `${(availKB / 1024 / 1024).toFixed(1)}GB available` };
|
|
14826
|
+
}
|
|
14827
|
+
} catch {
|
|
14828
|
+
}
|
|
14829
|
+
return null;
|
|
14830
|
+
}
|
|
14831
|
+
function checkErrorLog() {
|
|
14832
|
+
const checks = [];
|
|
14833
|
+
const errors = getRecentErrors();
|
|
14834
|
+
if (!errors) {
|
|
14835
|
+
checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
|
|
14836
|
+
return checks;
|
|
14837
|
+
}
|
|
14838
|
+
if (errors.rate429.length > 10) {
|
|
14839
|
+
checks.push({ name: "Rate limits", status: "error", message: `${errors.rate429.length} rate-limit (429) errors in last 24h` });
|
|
14840
|
+
} else if (errors.rate429.length > 0) {
|
|
14841
|
+
checks.push({ name: "Rate limits", status: "warning", message: `${errors.rate429.length} rate-limit errors in last 24h` });
|
|
14842
|
+
}
|
|
14843
|
+
if (errors.contentSilence.length > 0) {
|
|
14844
|
+
checks.push({ name: "Content silence", status: "warning", message: `${errors.contentSilence.length} silence timeout(s) in last 24h` });
|
|
14845
|
+
}
|
|
14846
|
+
if (errors.spawnTimeout.length > 0) {
|
|
14847
|
+
checks.push({ name: "Spawn timeouts", status: "warning", message: `${errors.spawnTimeout.length} backend timeout(s) in last 24h` });
|
|
14848
|
+
}
|
|
14849
|
+
if (errors.other.length > 0) {
|
|
14850
|
+
checks.push({ name: "Other errors", status: "warning", message: `${errors.other.length} error(s) in last 24h` });
|
|
14851
|
+
}
|
|
14852
|
+
if (checks.length === 0) {
|
|
14853
|
+
checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
|
|
14854
|
+
}
|
|
14855
|
+
return checks;
|
|
14856
|
+
}
|
|
14857
|
+
function checkBackendCLIs() {
|
|
14858
|
+
const checks = [];
|
|
14859
|
+
const CLI_BINARIES = { claude: "claude", gemini: "gemini", codex: "codex", cursor: "agent" };
|
|
14860
|
+
let installed = 0;
|
|
14861
|
+
for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
|
|
14862
|
+
try {
|
|
14863
|
+
const path = execFileSync("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
14864
|
+
if (path) {
|
|
14865
|
+
checks.push({ name: `${label2} CLI`, status: "ok", message: path });
|
|
14866
|
+
installed++;
|
|
14867
|
+
}
|
|
14868
|
+
} catch {
|
|
14869
|
+
}
|
|
14870
|
+
}
|
|
14871
|
+
if (installed === 0) {
|
|
14872
|
+
checks.push({ name: "Backend CLIs", status: "error", message: "No backend CLIs found" });
|
|
14873
|
+
}
|
|
14874
|
+
return checks;
|
|
14875
|
+
}
|
|
14876
|
+
function checkOllamaServers() {
|
|
14877
|
+
try {
|
|
14878
|
+
const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
|
|
14879
|
+
const servers = OllamaStore.listServers();
|
|
14880
|
+
if (servers.length > 0) {
|
|
14881
|
+
const online = servers.filter((s) => s.status === "online");
|
|
14882
|
+
const models = OllamaStore.getAvailableModels();
|
|
14883
|
+
if (online.length === servers.length) {
|
|
14884
|
+
return { name: "Ollama", status: "ok", message: `${online.length} server(s) online, ${models.length} model(s)` };
|
|
14885
|
+
} else if (online.length > 0) {
|
|
14886
|
+
return { name: "Ollama", status: "warning", message: `${online.length}/${servers.length} server(s) online` };
|
|
14887
|
+
}
|
|
14888
|
+
return { name: "Ollama", status: "warning", message: `${servers.length} server(s) configured, all offline`, fix: "ollama serve" };
|
|
14889
|
+
}
|
|
14890
|
+
} catch {
|
|
14891
|
+
}
|
|
14892
|
+
return null;
|
|
14893
|
+
}
|
|
14894
|
+
function checkBackendLimitsAll() {
|
|
14895
|
+
const checks = [];
|
|
14896
|
+
try {
|
|
14897
|
+
const { getAllBackendIds: getAllBackendIds3 } = (init_backends(), __toCommonJS(backends_exports));
|
|
14898
|
+
const { checkBackendLimits: checkBackendLimits2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
14899
|
+
for (const backend2 of getAllBackendIds3()) {
|
|
14900
|
+
const limitMsg = checkBackendLimits2(backend2);
|
|
14901
|
+
if (limitMsg) {
|
|
14902
|
+
checks.push({ name: `${backend2} limits`, status: "warning", message: limitMsg });
|
|
14903
|
+
}
|
|
14904
|
+
}
|
|
14905
|
+
} catch {
|
|
14906
|
+
}
|
|
14907
|
+
return checks;
|
|
14908
|
+
}
|
|
14909
|
+
function checkSchedulerHealth() {
|
|
14910
|
+
const checks = [];
|
|
14911
|
+
try {
|
|
14912
|
+
const { getHealthReport: getHealthReport2 } = (init_health2(), __toCommonJS(health_exports2));
|
|
14913
|
+
const report = getHealthReport2();
|
|
14914
|
+
if (report.failingJobs.length > 0) {
|
|
14915
|
+
for (const j of report.failingJobs) {
|
|
14916
|
+
checks.push({
|
|
14917
|
+
name: `Job #${j.id}`,
|
|
14918
|
+
status: report.failingJobs.length > 2 ? "error" : "warning",
|
|
14919
|
+
message: `"${j.description.slice(0, 40)}": ${j.failures} consecutive failures`
|
|
14920
|
+
});
|
|
14921
|
+
}
|
|
14922
|
+
}
|
|
14923
|
+
checks.push({
|
|
14924
|
+
name: "Scheduler",
|
|
14925
|
+
status: report.status === "healthy" ? "ok" : "warning",
|
|
14926
|
+
message: `${report.activeJobs} jobs active, ${report.failingJobs.length} failing`
|
|
14927
|
+
});
|
|
14928
|
+
} catch {
|
|
14929
|
+
}
|
|
14930
|
+
return checks;
|
|
14931
|
+
}
|
|
14932
|
+
function checkTelegramThrottle() {
|
|
14933
|
+
try {
|
|
14934
|
+
const { getThrottleState: getThrottleState2 } = (init_telegram_throttle(), __toCommonJS(telegram_throttle_exports));
|
|
14935
|
+
const state = getThrottleState2();
|
|
14936
|
+
if (!state) return null;
|
|
14937
|
+
if (state.isPaused) {
|
|
14938
|
+
return {
|
|
14939
|
+
name: "Telegram send queue",
|
|
14940
|
+
status: "error",
|
|
14941
|
+
message: `PAUSED \u2014 rate-limited, ${state.pauseRemainingSec}s remaining, ${state.queueDepth} message(s) queued`
|
|
14942
|
+
};
|
|
14943
|
+
}
|
|
14944
|
+
if (state.queueDepth > 5) {
|
|
14945
|
+
return {
|
|
14946
|
+
name: "Telegram send queue",
|
|
14947
|
+
status: "warning",
|
|
14948
|
+
message: `${state.queueDepth} message(s) queued (pacing)`
|
|
14949
|
+
};
|
|
14950
|
+
}
|
|
14951
|
+
return null;
|
|
14952
|
+
} catch {
|
|
14953
|
+
return null;
|
|
14954
|
+
}
|
|
14955
|
+
}
|
|
14956
|
+
function runAllHealthChecks() {
|
|
14957
|
+
const checks = [];
|
|
14958
|
+
checks.push(...checkDatabase());
|
|
14959
|
+
const disk = checkDiskSpace();
|
|
14960
|
+
if (disk) checks.push(disk);
|
|
14961
|
+
checks.push(...checkErrorLog());
|
|
14962
|
+
const throttle = checkTelegramThrottle();
|
|
14963
|
+
if (throttle) checks.push(throttle);
|
|
14964
|
+
checks.push(...checkBackendLimitsAll());
|
|
14965
|
+
checks.push(...checkSchedulerHealth());
|
|
14966
|
+
const ollama2 = checkOllamaServers();
|
|
14967
|
+
if (ollama2) checks.push(ollama2);
|
|
14968
|
+
const errors = checks.filter((c) => c.status === "error").length;
|
|
14969
|
+
const warnings = checks.filter((c) => c.status === "warning").length;
|
|
14970
|
+
let statusLine;
|
|
14971
|
+
if (errors === 0 && warnings === 0) {
|
|
14972
|
+
statusLine = "\u2705 All systems OK";
|
|
14973
|
+
} else {
|
|
14974
|
+
const parts = [];
|
|
14975
|
+
if (errors > 0) parts.push(`${errors} error(s)`);
|
|
14976
|
+
if (warnings > 0) parts.push(`${warnings} warning(s)`);
|
|
14977
|
+
statusLine = `\u26A0\uFE0F ${parts.join(", ")}`;
|
|
14978
|
+
}
|
|
14979
|
+
return { checks, errors, warnings, statusLine };
|
|
14980
|
+
}
|
|
14981
|
+
function formatHealthForPrompt(summary) {
|
|
14982
|
+
const lines = [];
|
|
14983
|
+
for (const check of summary.checks) {
|
|
14984
|
+
const icon = check.status === "ok" ? "\u2705" : check.status === "warning" ? "\u26A0\uFE0F" : "\u274C";
|
|
14985
|
+
lines.push(`${icon} ${check.name}: ${check.message}`);
|
|
14986
|
+
}
|
|
14987
|
+
return lines.join("\n");
|
|
14988
|
+
}
|
|
14989
|
+
var init_checks = __esm({
|
|
14990
|
+
"src/health/checks.ts"() {
|
|
14991
|
+
"use strict";
|
|
14992
|
+
init_paths();
|
|
14993
|
+
}
|
|
14994
|
+
});
|
|
14995
|
+
|
|
14996
|
+
// src/bootstrap/heartbeat.ts
|
|
14997
|
+
var heartbeat_exports = {};
|
|
14998
|
+
__export(heartbeat_exports, {
|
|
14999
|
+
HEARTBEAT_JOB_TYPE: () => HEARTBEAT_JOB_TYPE,
|
|
15000
|
+
HEARTBEAT_OK: () => HEARTBEAT_OK,
|
|
15001
|
+
assembleHeartbeatPrompt: () => assembleHeartbeatPrompt,
|
|
15002
|
+
disableHeartbeat: () => disableHeartbeat,
|
|
15003
|
+
enableHeartbeat: () => enableHeartbeat,
|
|
15004
|
+
findHeartbeatJob: () => findHeartbeatJob,
|
|
15005
|
+
formatHeartbeatStatus: () => formatHeartbeatStatus,
|
|
15006
|
+
initHeartbeat: () => initHeartbeat,
|
|
15007
|
+
parseHeartbeatCommand: () => parseHeartbeatCommand,
|
|
15008
|
+
runHeartbeatNow: () => runHeartbeatNow,
|
|
15009
|
+
startAllHeartbeats: () => startAllHeartbeats,
|
|
15010
|
+
startHeartbeatForChat: () => startHeartbeatForChat,
|
|
15011
|
+
stopAllHeartbeats: () => stopAllHeartbeats,
|
|
15012
|
+
stopHeartbeatForChat: () => stopHeartbeatForChat,
|
|
15013
|
+
updateHeartbeatConfig: () => updateHeartbeatConfig
|
|
15014
|
+
});
|
|
15015
|
+
import { readFileSync as readFileSync9, existsSync as existsSync15 } from "fs";
|
|
14566
15016
|
import { join as join14 } from "path";
|
|
15017
|
+
function findHeartbeatJob() {
|
|
15018
|
+
try {
|
|
15019
|
+
const { getDb: getDb2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
15020
|
+
const db3 = getDb2();
|
|
15021
|
+
const row = db3.prepare(
|
|
15022
|
+
"SELECT * FROM jobs WHERE job_type = 'heartbeat' AND active = 1 ORDER BY id LIMIT 1"
|
|
15023
|
+
).get();
|
|
15024
|
+
if (!row) return null;
|
|
15025
|
+
const { getJobById: getJobById3 } = (init_store5(), __toCommonJS(store_exports5));
|
|
15026
|
+
return getJobById3(row.id) ?? null;
|
|
15027
|
+
} catch {
|
|
15028
|
+
return null;
|
|
15029
|
+
}
|
|
15030
|
+
}
|
|
15031
|
+
function enableHeartbeat(chatId, opts) {
|
|
15032
|
+
const existing = findHeartbeatJob();
|
|
15033
|
+
if (existing) {
|
|
15034
|
+
if (!existing.enabled) {
|
|
15035
|
+
const { resumeJob: resumeJob2 } = (init_cron(), __toCommonJS(cron_exports));
|
|
15036
|
+
resumeJob2(existing.id);
|
|
15037
|
+
log(`[heartbeat] Resumed job #${existing.id}`);
|
|
15038
|
+
}
|
|
15039
|
+
return existing.id;
|
|
15040
|
+
}
|
|
15041
|
+
const { insertJob: insertJob2 } = (init_jobs(), __toCommonJS(jobs_exports));
|
|
15042
|
+
const { startSingleJob: startSingleJob2 } = (init_cron(), __toCommonJS(cron_exports));
|
|
15043
|
+
const intervalMs = opts?.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
15044
|
+
const job = insertJob2({
|
|
15045
|
+
scheduleType: "every",
|
|
15046
|
+
everyMs: intervalMs,
|
|
15047
|
+
description: DEFAULT_DESCRIPTION,
|
|
15048
|
+
chatId,
|
|
15049
|
+
backend: opts?.backend ?? null,
|
|
15050
|
+
model: opts?.model ?? null,
|
|
15051
|
+
sessionType: "isolated",
|
|
15052
|
+
deliveryMode: "announce",
|
|
15053
|
+
channel: opts?.channel ?? "telegram",
|
|
15054
|
+
target: opts?.target ?? chatId,
|
|
15055
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
|
|
15056
|
+
jobType: HEARTBEAT_JOB_TYPE,
|
|
15057
|
+
timeout: 120
|
|
15058
|
+
});
|
|
15059
|
+
startSingleJob2(job);
|
|
15060
|
+
log(`[heartbeat] Created job #${job.id} (every ${intervalMs / 6e4}min)`);
|
|
15061
|
+
return job.id;
|
|
15062
|
+
}
|
|
15063
|
+
function disableHeartbeat() {
|
|
15064
|
+
const job = findHeartbeatJob();
|
|
15065
|
+
if (!job) return false;
|
|
15066
|
+
const { pauseJob: pauseJob2 } = (init_cron(), __toCommonJS(cron_exports));
|
|
15067
|
+
pauseJob2(job.id);
|
|
15068
|
+
log(`[heartbeat] Paused job #${job.id}`);
|
|
15069
|
+
return true;
|
|
15070
|
+
}
|
|
15071
|
+
async function runHeartbeatNow() {
|
|
15072
|
+
const job = findHeartbeatJob();
|
|
15073
|
+
if (!job) return;
|
|
15074
|
+
const { triggerJob: triggerJob2 } = (init_cron(), __toCommonJS(cron_exports));
|
|
15075
|
+
await triggerJob2(job.id);
|
|
15076
|
+
}
|
|
15077
|
+
function updateHeartbeatConfig(updates) {
|
|
15078
|
+
const job = findHeartbeatJob();
|
|
15079
|
+
if (!job) return false;
|
|
15080
|
+
const { getDb: getDb2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
15081
|
+
const db3 = getDb2();
|
|
15082
|
+
const sets = [];
|
|
15083
|
+
const values = [];
|
|
15084
|
+
if (updates.intervalMs !== void 0) {
|
|
15085
|
+
sets.push("every_ms = ?");
|
|
15086
|
+
values.push(updates.intervalMs);
|
|
15087
|
+
}
|
|
15088
|
+
if (updates.backend !== void 0) {
|
|
15089
|
+
sets.push("backend = ?");
|
|
15090
|
+
values.push(updates.backend);
|
|
15091
|
+
}
|
|
15092
|
+
if (updates.model !== void 0) {
|
|
15093
|
+
sets.push("model = ?");
|
|
15094
|
+
values.push(updates.model);
|
|
15095
|
+
}
|
|
15096
|
+
if (updates.target !== void 0) {
|
|
15097
|
+
sets.push("target = ?");
|
|
15098
|
+
values.push(updates.target);
|
|
15099
|
+
}
|
|
15100
|
+
if (updates.channel !== void 0) {
|
|
15101
|
+
sets.push("channel = ?");
|
|
15102
|
+
values.push(updates.channel);
|
|
15103
|
+
}
|
|
15104
|
+
if (sets.length === 0) return false;
|
|
15105
|
+
values.push(job.id);
|
|
15106
|
+
db3.prepare(`UPDATE jobs SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
15107
|
+
if (job.enabled) {
|
|
15108
|
+
const { stopJobTimer: stopJobTimer2, startSingleJob: startSingleJob2, listJobs: listJobs3 } = (init_cron(), __toCommonJS(cron_exports));
|
|
15109
|
+
const { getJobById: getJobById3 } = (init_store5(), __toCommonJS(store_exports5));
|
|
15110
|
+
stopJobTimer2(job.id);
|
|
15111
|
+
const refreshed = getJobById3(job.id);
|
|
15112
|
+
if (refreshed) startSingleJob2(refreshed);
|
|
15113
|
+
}
|
|
15114
|
+
return true;
|
|
15115
|
+
}
|
|
15116
|
+
function initHeartbeat(_channelReg) {
|
|
15117
|
+
}
|
|
15118
|
+
function startAllHeartbeats() {
|
|
15119
|
+
}
|
|
15120
|
+
function stopAllHeartbeats() {
|
|
15121
|
+
}
|
|
15122
|
+
function startHeartbeatForChat(chatId) {
|
|
15123
|
+
enableHeartbeat(chatId);
|
|
15124
|
+
}
|
|
15125
|
+
function stopHeartbeatForChat(_chatId) {
|
|
15126
|
+
disableHeartbeat();
|
|
15127
|
+
}
|
|
15128
|
+
function assembleHeartbeatPrompt(chatId) {
|
|
15129
|
+
cleanExpiredWatches();
|
|
15130
|
+
const sections = [];
|
|
15131
|
+
sections.push(
|
|
15132
|
+
"You are running a periodic heartbeat check. Review the following system health data and active watches. If nothing needs the user's attention, respond with exactly HEARTBEAT_OK and nothing else. Only alert on genuine issues \u2014 do NOT report that everything is fine."
|
|
15133
|
+
);
|
|
15134
|
+
const healthSummary = runAllHealthChecks();
|
|
15135
|
+
const healthText = formatHealthForPrompt(healthSummary);
|
|
15136
|
+
sections.push(`[System Health Report]
|
|
15137
|
+
${healthText}`);
|
|
15138
|
+
const watches = getActiveWatches(chatId);
|
|
15139
|
+
if (watches.length > 0) {
|
|
15140
|
+
const watchLines = watches.map((w, i) => {
|
|
15141
|
+
const expiry = w.expiresAt ? ` (expires: ${w.expiresAt})` : "";
|
|
15142
|
+
return `${i + 1}. ${w.description}${expiry}`;
|
|
15143
|
+
});
|
|
15144
|
+
sections.push(`[Active Watches \u2014 execute these checks using your tools]
|
|
15145
|
+
${watchLines.join("\n")}`);
|
|
15146
|
+
}
|
|
15147
|
+
if (existsSync15(HEARTBEAT_MD_PATH)) {
|
|
15148
|
+
try {
|
|
15149
|
+
const custom = readFileSync9(HEARTBEAT_MD_PATH, "utf-8").trim();
|
|
15150
|
+
if (custom) {
|
|
15151
|
+
sections.push(`[Custom checks from HEARTBEAT.md]
|
|
15152
|
+
${custom}`);
|
|
15153
|
+
}
|
|
15154
|
+
} catch {
|
|
15155
|
+
}
|
|
15156
|
+
}
|
|
15157
|
+
return sections.join("\n\n");
|
|
15158
|
+
}
|
|
15159
|
+
function formatHeartbeatStatus(chatId) {
|
|
15160
|
+
const job = findHeartbeatJob();
|
|
15161
|
+
const watches = getActiveWatches(chatId);
|
|
15162
|
+
if (!job) {
|
|
15163
|
+
const lines2 = ["Heartbeat: not configured", "Use /heartbeat on to enable."];
|
|
15164
|
+
if (watches.length > 0) {
|
|
15165
|
+
lines2.push("", `Active watches (${watches.length}):`);
|
|
15166
|
+
for (const w of watches) {
|
|
15167
|
+
const expiry = w.expiresAt ? ` (until ${w.expiresAt})` : "";
|
|
15168
|
+
lines2.push(` - ${w.description}${expiry}`);
|
|
15169
|
+
}
|
|
15170
|
+
}
|
|
15171
|
+
return lines2.join("\n");
|
|
15172
|
+
}
|
|
15173
|
+
const lines = [
|
|
15174
|
+
`Heartbeat: ${job.enabled ? "ON" : "PAUSED"} (job #${job.id})`,
|
|
15175
|
+
`Interval: ${(job.everyMs ?? DEFAULT_INTERVAL_MS) / 6e4} minutes`,
|
|
15176
|
+
`Last run: ${job.lastRunAt ?? "never"}`,
|
|
15177
|
+
`Next run: ${job.nextRunAt ?? "N/A"}`
|
|
15178
|
+
];
|
|
15179
|
+
if (job.backend || job.model) {
|
|
15180
|
+
lines.push(`Backend: ${job.backend ?? "default"} | Model: ${job.model ?? "default"}`);
|
|
15181
|
+
}
|
|
15182
|
+
if (job.target && job.target !== chatId) {
|
|
15183
|
+
lines.push(`Delivery: ${job.target}`);
|
|
15184
|
+
}
|
|
15185
|
+
if (job.fallbacks?.length > 0) {
|
|
15186
|
+
lines.push(`Fallbacks: ${job.fallbacks.map((f) => `${f.backend}${f.model ? `:${f.model}` : ""}`).join(", ")}`);
|
|
15187
|
+
}
|
|
15188
|
+
if (watches.length > 0) {
|
|
15189
|
+
lines.push("", `Active watches (${watches.length}):`);
|
|
15190
|
+
for (const w of watches) {
|
|
15191
|
+
const expiry = w.expiresAt ? ` (until ${w.expiresAt})` : "";
|
|
15192
|
+
lines.push(` - ${w.description}${expiry}`);
|
|
15193
|
+
}
|
|
15194
|
+
}
|
|
15195
|
+
return lines.join("\n");
|
|
15196
|
+
}
|
|
15197
|
+
function parseHeartbeatCommand(_chatId, args) {
|
|
15198
|
+
const parts = args.trim().toLowerCase().split(/\s+/);
|
|
15199
|
+
return { action: parts[0] ?? "status", value: parts.slice(1).join(" ") };
|
|
15200
|
+
}
|
|
15201
|
+
var HEARTBEAT_MD_PATH, HEARTBEAT_OK, HEARTBEAT_JOB_TYPE, DEFAULT_INTERVAL_MS, DEFAULT_DESCRIPTION;
|
|
15202
|
+
var init_heartbeat2 = __esm({
|
|
15203
|
+
"src/bootstrap/heartbeat.ts"() {
|
|
15204
|
+
"use strict";
|
|
15205
|
+
init_paths();
|
|
15206
|
+
init_store5();
|
|
15207
|
+
init_checks();
|
|
15208
|
+
init_log();
|
|
15209
|
+
HEARTBEAT_MD_PATH = join14(WORKSPACE_PATH, "HEARTBEAT.md");
|
|
15210
|
+
HEARTBEAT_OK = "HEARTBEAT_OK";
|
|
15211
|
+
HEARTBEAT_JOB_TYPE = "heartbeat";
|
|
15212
|
+
DEFAULT_INTERVAL_MS = 30 * 60 * 1e3;
|
|
15213
|
+
DEFAULT_DESCRIPTION = "System heartbeat \u2014 health checks and watch tasks";
|
|
15214
|
+
}
|
|
15215
|
+
});
|
|
15216
|
+
|
|
15217
|
+
// src/bootstrap/profile.ts
|
|
15218
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync16 } from "fs";
|
|
15219
|
+
import { join as join15 } from "path";
|
|
14567
15220
|
function hasActiveProfile(chatId) {
|
|
14568
15221
|
return activeProfiles.has(chatId);
|
|
14569
15222
|
}
|
|
@@ -14692,8 +15345,8 @@ function extractUserUpdates(text) {
|
|
|
14692
15345
|
return { cleanText, updates };
|
|
14693
15346
|
}
|
|
14694
15347
|
function appendToUserProfile(key, value) {
|
|
14695
|
-
if (!
|
|
14696
|
-
const content =
|
|
15348
|
+
if (!existsSync16(USER_PATH2)) return;
|
|
15349
|
+
const content = readFileSync10(USER_PATH2, "utf-8");
|
|
14697
15350
|
const line = `- **${key}**: ${value}`;
|
|
14698
15351
|
if (content.includes(line)) return;
|
|
14699
15352
|
const updated = content.trimEnd() + `
|
|
@@ -14708,7 +15361,7 @@ var init_profile = __esm({
|
|
|
14708
15361
|
"use strict";
|
|
14709
15362
|
init_paths();
|
|
14710
15363
|
init_log();
|
|
14711
|
-
USER_PATH2 =
|
|
15364
|
+
USER_PATH2 = join15(IDENTITY_PATH, "USER.md");
|
|
14712
15365
|
activeProfiles = /* @__PURE__ */ new Map();
|
|
14713
15366
|
}
|
|
14714
15367
|
});
|
|
@@ -15745,8 +16398,8 @@ async function handleAdd(chatId, channel, name, host, port) {
|
|
|
15745
16398
|
}
|
|
15746
16399
|
const models = await OllamaService.discoverModels(name);
|
|
15747
16400
|
try {
|
|
15748
|
-
const { getAdapter:
|
|
15749
|
-
const adapter =
|
|
16401
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
16402
|
+
const adapter = getAdapter4("ollama");
|
|
15750
16403
|
if ("refreshModelCatalog" in adapter) {
|
|
15751
16404
|
adapter.refreshModelCatalog();
|
|
15752
16405
|
}
|
|
@@ -15799,8 +16452,8 @@ async function sendDiscover(chatId, channel, serverName) {
|
|
|
15799
16452
|
const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
|
|
15800
16453
|
const models = await OllamaService.discoverModels(serverName);
|
|
15801
16454
|
try {
|
|
15802
|
-
const { getAdapter:
|
|
15803
|
-
const adapter =
|
|
16455
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
16456
|
+
const adapter = getAdapter4("ollama");
|
|
15804
16457
|
if ("refreshModelCatalog" in adapter) {
|
|
15805
16458
|
adapter.refreshModelCatalog();
|
|
15806
16459
|
}
|
|
@@ -16005,8 +16658,8 @@ __export(session_log_exports2, {
|
|
|
16005
16658
|
startSessionLogCleanupTimer: () => startSessionLogCleanupTimer,
|
|
16006
16659
|
tailSessionLog: () => tailSessionLog
|
|
16007
16660
|
});
|
|
16008
|
-
import { existsSync as
|
|
16009
|
-
import { join as
|
|
16661
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, appendFileSync, readdirSync as readdirSync9, unlinkSync as unlinkSync6, statSync as statSync7, createReadStream } from "fs";
|
|
16662
|
+
import { join as join16, basename } from "path";
|
|
16010
16663
|
import { createInterface as createInterface6 } from "readline";
|
|
16011
16664
|
function getRetentionDays() {
|
|
16012
16665
|
const env = process.env.SESSION_LOG_RETENTION_DAYS;
|
|
@@ -16018,15 +16671,15 @@ function getRetentionDays() {
|
|
|
16018
16671
|
}
|
|
16019
16672
|
function cleanupSessionLogs(retentionDays) {
|
|
16020
16673
|
const days = retentionDays ?? getRetentionDays();
|
|
16021
|
-
if (!
|
|
16674
|
+
if (!existsSync17(SESSION_LOGS_PATH)) return 0;
|
|
16022
16675
|
const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
16023
16676
|
let cleaned = 0;
|
|
16024
16677
|
try {
|
|
16025
16678
|
for (const file of readdirSync9(SESSION_LOGS_PATH)) {
|
|
16026
16679
|
if (!file.startsWith("session-") || !file.endsWith(".log")) continue;
|
|
16027
|
-
const filePath =
|
|
16680
|
+
const filePath = join16(SESSION_LOGS_PATH, file);
|
|
16028
16681
|
try {
|
|
16029
|
-
const { mtimeMs } =
|
|
16682
|
+
const { mtimeMs } = statSync7(filePath);
|
|
16030
16683
|
if (mtimeMs < cutoff) {
|
|
16031
16684
|
unlinkSync6(filePath);
|
|
16032
16685
|
cleaned++;
|
|
@@ -16050,13 +16703,13 @@ function startSessionLogCleanupTimer() {
|
|
|
16050
16703
|
return timer;
|
|
16051
16704
|
}
|
|
16052
16705
|
function listSessionLogs() {
|
|
16053
|
-
if (!
|
|
16706
|
+
if (!existsSync17(SESSION_LOGS_PATH)) return [];
|
|
16054
16707
|
const logs = [];
|
|
16055
16708
|
for (const file of readdirSync9(SESSION_LOGS_PATH)) {
|
|
16056
16709
|
if (!file.startsWith("session-") || !file.endsWith(".log")) continue;
|
|
16057
|
-
const filePath =
|
|
16710
|
+
const filePath = join16(SESSION_LOGS_PATH, file);
|
|
16058
16711
|
try {
|
|
16059
|
-
const stat3 =
|
|
16712
|
+
const stat3 = statSync7(filePath);
|
|
16060
16713
|
const match = file.match(/^session-(.+?)-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})\.log$/);
|
|
16061
16714
|
logs.push({
|
|
16062
16715
|
filename: file,
|
|
@@ -16073,7 +16726,7 @@ function listSessionLogs() {
|
|
|
16073
16726
|
return logs;
|
|
16074
16727
|
}
|
|
16075
16728
|
async function* tailSessionLog(filePath, lines = 50) {
|
|
16076
|
-
if (!
|
|
16729
|
+
if (!existsSync17(filePath)) {
|
|
16077
16730
|
yield `File not found: ${filePath}`;
|
|
16078
16731
|
return;
|
|
16079
16732
|
}
|
|
@@ -16101,12 +16754,12 @@ var init_session_log2 = __esm({
|
|
|
16101
16754
|
constructor(chatId, backend2, model2) {
|
|
16102
16755
|
this.backend = backend2;
|
|
16103
16756
|
this.model = model2;
|
|
16104
|
-
if (!
|
|
16757
|
+
if (!existsSync17(SESSION_LOGS_PATH)) {
|
|
16105
16758
|
mkdirSync8(SESSION_LOGS_PATH, { recursive: true });
|
|
16106
16759
|
}
|
|
16107
16760
|
const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
16108
16761
|
const sanitizedChatId = chatId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
16109
|
-
this.filePath =
|
|
16762
|
+
this.filePath = join16(SESSION_LOGS_PATH, `session-${sanitizedChatId}-${ts2}.log`);
|
|
16110
16763
|
const header2 = [
|
|
16111
16764
|
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
16112
16765
|
`CC-Claw Agent Session \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
@@ -16508,14 +17161,14 @@ var init_gate = __esm({
|
|
|
16508
17161
|
|
|
16509
17162
|
// src/voice/stt.ts
|
|
16510
17163
|
import crypto from "crypto";
|
|
16511
|
-
import { execFile as execFile2, execFileSync } from "child_process";
|
|
17164
|
+
import { execFile as execFile2, execFileSync as execFileSync2 } from "child_process";
|
|
16512
17165
|
import { readFile as readFile2, unlink } from "fs/promises";
|
|
16513
17166
|
import { promisify as promisify2 } from "util";
|
|
16514
17167
|
function ensureFfmpeg() {
|
|
16515
17168
|
if (ffmpegAvailable === true) return;
|
|
16516
17169
|
if (ffmpegAvailable === false) throw new Error("ffmpeg is required for voice replies. Install it: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)");
|
|
16517
17170
|
try {
|
|
16518
|
-
|
|
17171
|
+
execFileSync2("ffmpeg", ["-version"], { stdio: "ignore" });
|
|
16519
17172
|
ffmpegAvailable = true;
|
|
16520
17173
|
} catch {
|
|
16521
17174
|
ffmpegAvailable = false;
|
|
@@ -16714,9 +17367,9 @@ var init_stt = __esm({
|
|
|
16714
17367
|
});
|
|
16715
17368
|
|
|
16716
17369
|
// src/media/image-gen.ts
|
|
16717
|
-
import { mkdirSync as mkdirSync9, existsSync as
|
|
17370
|
+
import { mkdirSync as mkdirSync9, existsSync as existsSync18, unlink as unlink2, readdir, stat } from "fs";
|
|
16718
17371
|
import { writeFile } from "fs/promises";
|
|
16719
|
-
import { join as
|
|
17372
|
+
import { join as join17 } from "path";
|
|
16720
17373
|
async function generateImage(prompt) {
|
|
16721
17374
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
16722
17375
|
if (!apiKey) {
|
|
@@ -16763,12 +17416,12 @@ async function generateImage(prompt) {
|
|
|
16763
17416
|
if (!imageData) {
|
|
16764
17417
|
throw new Error(textResponse ?? "Gemini did not generate an image. The prompt may have been filtered.");
|
|
16765
17418
|
}
|
|
16766
|
-
if (!
|
|
17419
|
+
if (!existsSync18(IMAGE_OUTPUT_DIR)) {
|
|
16767
17420
|
mkdirSync9(IMAGE_OUTPUT_DIR, { recursive: true });
|
|
16768
17421
|
}
|
|
16769
17422
|
const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
|
|
16770
17423
|
const filename = `img_${Date.now()}.${ext}`;
|
|
16771
|
-
const filePath =
|
|
17424
|
+
const filePath = join17(IMAGE_OUTPUT_DIR, filename);
|
|
16772
17425
|
const buffer = Buffer.from(imageData, "base64");
|
|
16773
17426
|
await writeFile(filePath, buffer);
|
|
16774
17427
|
log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
|
|
@@ -16786,7 +17439,7 @@ function cleanupGeneratedImage(filePath) {
|
|
|
16786
17439
|
function pruneImageCache() {
|
|
16787
17440
|
readdir(IMAGE_OUTPUT_DIR, (err, files) => {
|
|
16788
17441
|
if (err || !files) return;
|
|
16789
|
-
const imageFiles = files.filter((f) => /\.(png|jpg)$/.test(f)).map((f) =>
|
|
17442
|
+
const imageFiles = files.filter((f) => /\.(png|jpg)$/.test(f)).map((f) => join17(IMAGE_OUTPUT_DIR, f));
|
|
16790
17443
|
if (imageFiles.length === 0) return;
|
|
16791
17444
|
const now = Date.now();
|
|
16792
17445
|
let statsPending = imageFiles.length;
|
|
@@ -16818,8 +17471,8 @@ var init_image_gen = __esm({
|
|
|
16818
17471
|
MAX_GENERATED_IMAGES = 20;
|
|
16819
17472
|
IMAGE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
16820
17473
|
IMAGE_MODEL = "gemini-3.1-flash-image-preview";
|
|
16821
|
-
IMAGE_OUTPUT_DIR =
|
|
16822
|
-
process.env.CC_CLAW_HOME ??
|
|
17474
|
+
IMAGE_OUTPUT_DIR = join17(
|
|
17475
|
+
process.env.CC_CLAW_HOME ?? join17(process.env.HOME ?? "/tmp", ".cc-claw"),
|
|
16823
17476
|
"data",
|
|
16824
17477
|
"images"
|
|
16825
17478
|
);
|
|
@@ -17276,7 +17929,7 @@ var init_video = __esm({
|
|
|
17276
17929
|
});
|
|
17277
17930
|
|
|
17278
17931
|
// src/router/media.ts
|
|
17279
|
-
import { join as
|
|
17932
|
+
import { join as join18 } from "path";
|
|
17280
17933
|
import { mkdir, writeFile as writeFile2, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
|
|
17281
17934
|
function getMediaRetentionMs() {
|
|
17282
17935
|
const hours = parseInt(process.env.MEDIA_RETENTION_HOURS ?? "24", 10);
|
|
@@ -17285,7 +17938,7 @@ function getMediaRetentionMs() {
|
|
|
17285
17938
|
async function saveMedia(buffer, prefix, ext) {
|
|
17286
17939
|
await mkdir(MEDIA_INCOMING_PATH, { recursive: true });
|
|
17287
17940
|
const filename = `${prefix}-${Date.now()}.${ext}`;
|
|
17288
|
-
const fullPath =
|
|
17941
|
+
const fullPath = join18(MEDIA_INCOMING_PATH, filename);
|
|
17289
17942
|
await writeFile2(fullPath, buffer);
|
|
17290
17943
|
return fullPath;
|
|
17291
17944
|
}
|
|
@@ -17299,7 +17952,7 @@ async function cleanupOldMedia() {
|
|
|
17299
17952
|
let removed = 0;
|
|
17300
17953
|
for (const file of files) {
|
|
17301
17954
|
try {
|
|
17302
|
-
const filePath =
|
|
17955
|
+
const filePath = join18(MEDIA_INCOMING_PATH, file);
|
|
17303
17956
|
const s = await stat2(filePath);
|
|
17304
17957
|
if (now - s.mtimeMs > retentionMs) {
|
|
17305
17958
|
await unlink3(filePath);
|
|
@@ -17535,7 +18188,7 @@ var init_media = __esm({
|
|
|
17535
18188
|
init_helpers();
|
|
17536
18189
|
init_response();
|
|
17537
18190
|
init_live_status();
|
|
17538
|
-
MEDIA_INCOMING_PATH =
|
|
18191
|
+
MEDIA_INCOMING_PATH = join18(MEDIA_PATH, "incoming");
|
|
17539
18192
|
}
|
|
17540
18193
|
});
|
|
17541
18194
|
|
|
@@ -17848,9 +18501,9 @@ __export(install_exports, {
|
|
|
17848
18501
|
installSkillFromGitHub: () => installSkillFromGitHub
|
|
17849
18502
|
});
|
|
17850
18503
|
import { mkdir as mkdir2, readdir as readdir3, readFile as readFile4, cp } from "fs/promises";
|
|
17851
|
-
import { existsSync as
|
|
17852
|
-
import { join as
|
|
17853
|
-
import { execSync as
|
|
18504
|
+
import { existsSync as existsSync19 } from "fs";
|
|
18505
|
+
import { join as join19, basename as basename2 } from "path";
|
|
18506
|
+
import { execSync as execSync4 } from "child_process";
|
|
17854
18507
|
async function installSkillFromGitHub(urlOrShorthand) {
|
|
17855
18508
|
let repoUrl;
|
|
17856
18509
|
let subPath;
|
|
@@ -17860,36 +18513,36 @@ async function installSkillFromGitHub(urlOrShorthand) {
|
|
|
17860
18513
|
}
|
|
17861
18514
|
repoUrl = parsed.cloneUrl;
|
|
17862
18515
|
subPath = parsed.subPath;
|
|
17863
|
-
const tmpDir =
|
|
18516
|
+
const tmpDir = join19("/tmp", `cc-claw-skill-${Date.now()}`);
|
|
17864
18517
|
try {
|
|
17865
18518
|
log(`[skill-install] Cloning ${repoUrl} to ${tmpDir}`);
|
|
17866
|
-
|
|
18519
|
+
execSync4(`git clone --depth 1 ${repoUrl} ${tmpDir}`, {
|
|
17867
18520
|
stdio: "pipe",
|
|
17868
18521
|
timeout: 3e4
|
|
17869
18522
|
});
|
|
17870
|
-
if (!
|
|
18523
|
+
if (!existsSync19(join19(tmpDir, ".git"))) {
|
|
17871
18524
|
return { success: false, error: "Git clone failed: no .git directory produced" };
|
|
17872
18525
|
}
|
|
17873
|
-
const searchRoot = subPath ?
|
|
18526
|
+
const searchRoot = subPath ? join19(tmpDir, subPath) : tmpDir;
|
|
17874
18527
|
const skillDir = await findSkillDir(searchRoot);
|
|
17875
18528
|
if (!skillDir) {
|
|
17876
18529
|
return { success: false, error: "No SKILL.md found in the repository." };
|
|
17877
18530
|
}
|
|
17878
18531
|
const skillFolderName = basename2(skillDir);
|
|
17879
|
-
const destDir =
|
|
17880
|
-
if (
|
|
18532
|
+
const destDir = join19(SKILLS_PATH, skillFolderName);
|
|
18533
|
+
if (existsSync19(destDir)) {
|
|
17881
18534
|
log(`[skill-install] Overwriting existing skill at ${destDir}`);
|
|
17882
18535
|
}
|
|
17883
18536
|
await mkdir2(destDir, { recursive: true });
|
|
17884
18537
|
await cp(skillDir, destDir, { recursive: true });
|
|
17885
18538
|
let skillName = skillFolderName;
|
|
17886
18539
|
try {
|
|
17887
|
-
const content = await readFile4(
|
|
18540
|
+
const content = await readFile4(join19(destDir, "SKILL.md"), "utf-8");
|
|
17888
18541
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
17889
18542
|
if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
17890
18543
|
} catch {
|
|
17891
18544
|
try {
|
|
17892
|
-
const content = await readFile4(
|
|
18545
|
+
const content = await readFile4(join19(destDir, "skill.md"), "utf-8");
|
|
17893
18546
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
17894
18547
|
if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
17895
18548
|
} catch {
|
|
@@ -17902,7 +18555,7 @@ async function installSkillFromGitHub(urlOrShorthand) {
|
|
|
17902
18555
|
return { success: false, error: errorMessage(err) };
|
|
17903
18556
|
} finally {
|
|
17904
18557
|
try {
|
|
17905
|
-
|
|
18558
|
+
execSync4(`rm -rf ${tmpDir}`, { stdio: "pipe" });
|
|
17906
18559
|
} catch {
|
|
17907
18560
|
}
|
|
17908
18561
|
}
|
|
@@ -17924,15 +18577,15 @@ function parseGitHubUrl(input) {
|
|
|
17924
18577
|
async function findSkillDir(root) {
|
|
17925
18578
|
const candidates = ["SKILL.md", "skill.md"];
|
|
17926
18579
|
for (const c of candidates) {
|
|
17927
|
-
if (
|
|
18580
|
+
if (existsSync19(join19(root, c))) return root;
|
|
17928
18581
|
}
|
|
17929
18582
|
try {
|
|
17930
18583
|
const entries = await readdir3(root, { withFileTypes: true });
|
|
17931
18584
|
for (const entry of entries) {
|
|
17932
18585
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
17933
18586
|
for (const c of candidates) {
|
|
17934
|
-
if (
|
|
17935
|
-
return
|
|
18587
|
+
if (existsSync19(join19(root, entry.name, c))) {
|
|
18588
|
+
return join19(root, entry.name);
|
|
17936
18589
|
}
|
|
17937
18590
|
}
|
|
17938
18591
|
}
|
|
@@ -17944,15 +18597,15 @@ async function findSkillDir(root) {
|
|
|
17944
18597
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
17945
18598
|
let subEntries;
|
|
17946
18599
|
try {
|
|
17947
|
-
subEntries = await readdir3(
|
|
18600
|
+
subEntries = await readdir3(join19(root, entry.name), { withFileTypes: true });
|
|
17948
18601
|
} catch {
|
|
17949
18602
|
continue;
|
|
17950
18603
|
}
|
|
17951
18604
|
for (const sub of subEntries) {
|
|
17952
18605
|
if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
|
|
17953
18606
|
for (const c of candidates) {
|
|
17954
|
-
if (
|
|
17955
|
-
return
|
|
18607
|
+
if (existsSync19(join19(root, entry.name, sub.name, c))) {
|
|
18608
|
+
return join19(root, entry.name, sub.name);
|
|
17956
18609
|
}
|
|
17957
18610
|
}
|
|
17958
18611
|
}
|
|
@@ -17969,444 +18622,6 @@ var init_install = __esm({
|
|
|
17969
18622
|
}
|
|
17970
18623
|
});
|
|
17971
18624
|
|
|
17972
|
-
// src/health/checks.ts
|
|
17973
|
-
import { existsSync as existsSync18, statSync as statSync7, readFileSync as readFileSync9 } from "fs";
|
|
17974
|
-
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
17975
|
-
function getRecentErrors() {
|
|
17976
|
-
if (!existsSync18(ERROR_LOG_PATH)) return null;
|
|
17977
|
-
const logContent = readFileSync9(ERROR_LOG_PATH, "utf-8");
|
|
17978
|
-
const allLines = logContent.split("\n").filter(Boolean).slice(-500);
|
|
17979
|
-
const last24h = Date.now() - 864e5;
|
|
17980
|
-
const lines = allLines.filter((line) => {
|
|
17981
|
-
const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:+-]+)/);
|
|
17982
|
-
if (match) return (/* @__PURE__ */ new Date(`${match[1]}T${match[2]}`)).getTime() > last24h;
|
|
17983
|
-
return false;
|
|
17984
|
-
});
|
|
17985
|
-
if (lines.length === 0) return null;
|
|
17986
|
-
const classified = { rate429: [], contentSilence: [], spawnTimeout: [], other: [], total: 0 };
|
|
17987
|
-
for (const line of lines) {
|
|
17988
|
-
if (/429|rate.?limit/i.test(line)) classified.rate429.push(line);
|
|
17989
|
-
else if (/content silence/i.test(line)) classified.contentSilence.push(line);
|
|
17990
|
-
else if (/spawn timeout|timeout after \d+s/i.test(line)) classified.spawnTimeout.push(line);
|
|
17991
|
-
else classified.other.push(line);
|
|
17992
|
-
}
|
|
17993
|
-
classified.total = lines.length;
|
|
17994
|
-
return classified;
|
|
17995
|
-
}
|
|
17996
|
-
function checkDatabase() {
|
|
17997
|
-
const checks = [];
|
|
17998
|
-
if (existsSync18(DB_PATH)) {
|
|
17999
|
-
const size = statSync7(DB_PATH).size;
|
|
18000
|
-
checks.push({ name: "Database", status: "ok", message: `${(size / 1024).toFixed(0)}KB` });
|
|
18001
|
-
} else {
|
|
18002
|
-
checks.push({ name: "Database", status: "error", message: "Not found", fix: "cc-claw setup" });
|
|
18003
|
-
}
|
|
18004
|
-
return checks;
|
|
18005
|
-
}
|
|
18006
|
-
function checkDiskSpace() {
|
|
18007
|
-
try {
|
|
18008
|
-
const dfOutput = execSync4("df -k " + DATA_PATH, { encoding: "utf-8" });
|
|
18009
|
-
const lines = dfOutput.trim().split("\n");
|
|
18010
|
-
if (lines.length >= 2) {
|
|
18011
|
-
const parts = lines[1].split(/\s+/);
|
|
18012
|
-
const availKB = parseInt(parts[3], 10);
|
|
18013
|
-
if (availKB < 1e5) {
|
|
18014
|
-
return { name: "Disk space", status: "warning", message: `${(availKB / 1024).toFixed(0)}MB available` };
|
|
18015
|
-
}
|
|
18016
|
-
return { name: "Disk space", status: "ok", message: `${(availKB / 1024 / 1024).toFixed(1)}GB available` };
|
|
18017
|
-
}
|
|
18018
|
-
} catch {
|
|
18019
|
-
}
|
|
18020
|
-
return null;
|
|
18021
|
-
}
|
|
18022
|
-
function checkErrorLog() {
|
|
18023
|
-
const checks = [];
|
|
18024
|
-
const errors = getRecentErrors();
|
|
18025
|
-
if (!errors) {
|
|
18026
|
-
checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
|
|
18027
|
-
return checks;
|
|
18028
|
-
}
|
|
18029
|
-
if (errors.rate429.length > 10) {
|
|
18030
|
-
checks.push({ name: "Rate limits", status: "error", message: `${errors.rate429.length} rate-limit (429) errors in last 24h` });
|
|
18031
|
-
} else if (errors.rate429.length > 0) {
|
|
18032
|
-
checks.push({ name: "Rate limits", status: "warning", message: `${errors.rate429.length} rate-limit errors in last 24h` });
|
|
18033
|
-
}
|
|
18034
|
-
if (errors.contentSilence.length > 0) {
|
|
18035
|
-
checks.push({ name: "Content silence", status: "warning", message: `${errors.contentSilence.length} silence timeout(s) in last 24h` });
|
|
18036
|
-
}
|
|
18037
|
-
if (errors.spawnTimeout.length > 0) {
|
|
18038
|
-
checks.push({ name: "Spawn timeouts", status: "warning", message: `${errors.spawnTimeout.length} backend timeout(s) in last 24h` });
|
|
18039
|
-
}
|
|
18040
|
-
if (errors.other.length > 0) {
|
|
18041
|
-
checks.push({ name: "Other errors", status: "warning", message: `${errors.other.length} error(s) in last 24h` });
|
|
18042
|
-
}
|
|
18043
|
-
if (checks.length === 0) {
|
|
18044
|
-
checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
|
|
18045
|
-
}
|
|
18046
|
-
return checks;
|
|
18047
|
-
}
|
|
18048
|
-
function checkBackendCLIs() {
|
|
18049
|
-
const checks = [];
|
|
18050
|
-
const CLI_BINARIES = { claude: "claude", gemini: "gemini", codex: "codex", cursor: "agent" };
|
|
18051
|
-
let installed = 0;
|
|
18052
|
-
for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
|
|
18053
|
-
try {
|
|
18054
|
-
const path = execFileSync2("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
18055
|
-
if (path) {
|
|
18056
|
-
checks.push({ name: `${label2} CLI`, status: "ok", message: path });
|
|
18057
|
-
installed++;
|
|
18058
|
-
}
|
|
18059
|
-
} catch {
|
|
18060
|
-
}
|
|
18061
|
-
}
|
|
18062
|
-
if (installed === 0) {
|
|
18063
|
-
checks.push({ name: "Backend CLIs", status: "error", message: "No backend CLIs found" });
|
|
18064
|
-
}
|
|
18065
|
-
return checks;
|
|
18066
|
-
}
|
|
18067
|
-
function checkOllamaServers() {
|
|
18068
|
-
try {
|
|
18069
|
-
const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
|
|
18070
|
-
const servers = OllamaStore.listServers();
|
|
18071
|
-
if (servers.length > 0) {
|
|
18072
|
-
const online = servers.filter((s) => s.status === "online");
|
|
18073
|
-
const models = OllamaStore.getAvailableModels();
|
|
18074
|
-
if (online.length === servers.length) {
|
|
18075
|
-
return { name: "Ollama", status: "ok", message: `${online.length} server(s) online, ${models.length} model(s)` };
|
|
18076
|
-
} else if (online.length > 0) {
|
|
18077
|
-
return { name: "Ollama", status: "warning", message: `${online.length}/${servers.length} server(s) online` };
|
|
18078
|
-
}
|
|
18079
|
-
return { name: "Ollama", status: "warning", message: `${servers.length} server(s) configured, all offline`, fix: "ollama serve" };
|
|
18080
|
-
}
|
|
18081
|
-
} catch {
|
|
18082
|
-
}
|
|
18083
|
-
return null;
|
|
18084
|
-
}
|
|
18085
|
-
function checkBackendLimitsAll() {
|
|
18086
|
-
const checks = [];
|
|
18087
|
-
try {
|
|
18088
|
-
const { getAllBackendIds: getAllBackendIds3 } = (init_backends(), __toCommonJS(backends_exports));
|
|
18089
|
-
const { checkBackendLimits: checkBackendLimits2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
18090
|
-
for (const backend2 of getAllBackendIds3()) {
|
|
18091
|
-
const limitMsg = checkBackendLimits2(backend2);
|
|
18092
|
-
if (limitMsg) {
|
|
18093
|
-
checks.push({ name: `${backend2} limits`, status: "warning", message: limitMsg });
|
|
18094
|
-
}
|
|
18095
|
-
}
|
|
18096
|
-
} catch {
|
|
18097
|
-
}
|
|
18098
|
-
return checks;
|
|
18099
|
-
}
|
|
18100
|
-
function checkSchedulerHealth() {
|
|
18101
|
-
const checks = [];
|
|
18102
|
-
try {
|
|
18103
|
-
const { getHealthReport: getHealthReport2 } = (init_health2(), __toCommonJS(health_exports2));
|
|
18104
|
-
const report = getHealthReport2();
|
|
18105
|
-
if (report.failingJobs.length > 0) {
|
|
18106
|
-
for (const j of report.failingJobs) {
|
|
18107
|
-
checks.push({
|
|
18108
|
-
name: `Job #${j.id}`,
|
|
18109
|
-
status: report.failingJobs.length > 2 ? "error" : "warning",
|
|
18110
|
-
message: `"${j.description.slice(0, 40)}": ${j.failures} consecutive failures`
|
|
18111
|
-
});
|
|
18112
|
-
}
|
|
18113
|
-
}
|
|
18114
|
-
checks.push({
|
|
18115
|
-
name: "Scheduler",
|
|
18116
|
-
status: report.status === "healthy" ? "ok" : "warning",
|
|
18117
|
-
message: `${report.activeJobs} jobs active, ${report.failingJobs.length} failing`
|
|
18118
|
-
});
|
|
18119
|
-
} catch {
|
|
18120
|
-
}
|
|
18121
|
-
return checks;
|
|
18122
|
-
}
|
|
18123
|
-
function runAllHealthChecks() {
|
|
18124
|
-
const checks = [];
|
|
18125
|
-
checks.push(...checkDatabase());
|
|
18126
|
-
const disk = checkDiskSpace();
|
|
18127
|
-
if (disk) checks.push(disk);
|
|
18128
|
-
checks.push(...checkErrorLog());
|
|
18129
|
-
checks.push(...checkBackendLimitsAll());
|
|
18130
|
-
checks.push(...checkSchedulerHealth());
|
|
18131
|
-
const ollama2 = checkOllamaServers();
|
|
18132
|
-
if (ollama2) checks.push(ollama2);
|
|
18133
|
-
const errors = checks.filter((c) => c.status === "error").length;
|
|
18134
|
-
const warnings = checks.filter((c) => c.status === "warning").length;
|
|
18135
|
-
let statusLine;
|
|
18136
|
-
if (errors === 0 && warnings === 0) {
|
|
18137
|
-
statusLine = "\u2705 All systems OK";
|
|
18138
|
-
} else {
|
|
18139
|
-
const parts = [];
|
|
18140
|
-
if (errors > 0) parts.push(`${errors} error(s)`);
|
|
18141
|
-
if (warnings > 0) parts.push(`${warnings} warning(s)`);
|
|
18142
|
-
statusLine = `\u26A0\uFE0F ${parts.join(", ")}`;
|
|
18143
|
-
}
|
|
18144
|
-
return { checks, errors, warnings, statusLine };
|
|
18145
|
-
}
|
|
18146
|
-
function formatHealthForPrompt(summary) {
|
|
18147
|
-
const lines = [];
|
|
18148
|
-
for (const check of summary.checks) {
|
|
18149
|
-
const icon = check.status === "ok" ? "\u2705" : check.status === "warning" ? "\u26A0\uFE0F" : "\u274C";
|
|
18150
|
-
lines.push(`${icon} ${check.name}: ${check.message}`);
|
|
18151
|
-
}
|
|
18152
|
-
return lines.join("\n");
|
|
18153
|
-
}
|
|
18154
|
-
var init_checks = __esm({
|
|
18155
|
-
"src/health/checks.ts"() {
|
|
18156
|
-
"use strict";
|
|
18157
|
-
init_paths();
|
|
18158
|
-
}
|
|
18159
|
-
});
|
|
18160
|
-
|
|
18161
|
-
// src/bootstrap/heartbeat.ts
|
|
18162
|
-
import { readFileSync as readFileSync10, existsSync as existsSync19 } from "fs";
|
|
18163
|
-
import { join as join19 } from "path";
|
|
18164
|
-
function initHeartbeat(channelReg) {
|
|
18165
|
-
registry2 = channelReg;
|
|
18166
|
-
}
|
|
18167
|
-
function startHeartbeatForChat(chatId) {
|
|
18168
|
-
stopHeartbeatForChat(chatId);
|
|
18169
|
-
const config2 = getHeartbeatConfig(chatId);
|
|
18170
|
-
if (!config2 || !config2.enabled) return;
|
|
18171
|
-
let running = false;
|
|
18172
|
-
const timer = setInterval(async () => {
|
|
18173
|
-
if (running) {
|
|
18174
|
-
log(`[heartbeat] Skipping tick for ${chatId}: previous heartbeat still running`);
|
|
18175
|
-
return;
|
|
18176
|
-
}
|
|
18177
|
-
running = true;
|
|
18178
|
-
try {
|
|
18179
|
-
await runHeartbeat(chatId, config2);
|
|
18180
|
-
} finally {
|
|
18181
|
-
running = false;
|
|
18182
|
-
}
|
|
18183
|
-
}, config2.intervalMs);
|
|
18184
|
-
activeTimers.set(chatId, timer);
|
|
18185
|
-
const nextBeat = new Date(Date.now() + config2.intervalMs).toISOString();
|
|
18186
|
-
updateHeartbeatTimestamps(chatId, config2.lastBeatAt ?? "", nextBeat);
|
|
18187
|
-
log(`[heartbeat] Started for chat ${chatId} (every ${config2.intervalMs / 6e4}min)`);
|
|
18188
|
-
}
|
|
18189
|
-
function stopHeartbeatForChat(chatId) {
|
|
18190
|
-
const timer = activeTimers.get(chatId);
|
|
18191
|
-
if (timer) {
|
|
18192
|
-
clearInterval(timer);
|
|
18193
|
-
activeTimers.delete(chatId);
|
|
18194
|
-
}
|
|
18195
|
-
}
|
|
18196
|
-
function stopAllHeartbeats() {
|
|
18197
|
-
for (const [chatId] of activeTimers) {
|
|
18198
|
-
stopHeartbeatForChat(chatId);
|
|
18199
|
-
}
|
|
18200
|
-
}
|
|
18201
|
-
function startAllHeartbeats() {
|
|
18202
|
-
try {
|
|
18203
|
-
const db3 = getDb();
|
|
18204
|
-
const allowedIds = (process.env.ALLOWED_CHAT_ID ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
18205
|
-
for (const chatId of allowedIds) {
|
|
18206
|
-
const existing = getHeartbeatConfig(chatId);
|
|
18207
|
-
if (!existing) {
|
|
18208
|
-
setHeartbeatConfig(chatId, { enabled: true });
|
|
18209
|
-
log(`[heartbeat] Enabled by default for chat ${chatId}`);
|
|
18210
|
-
}
|
|
18211
|
-
}
|
|
18212
|
-
const rows = db3.prepare(
|
|
18213
|
-
"SELECT chat_id FROM chat_heartbeat WHERE enabled = 1"
|
|
18214
|
-
).all();
|
|
18215
|
-
for (const row of rows) {
|
|
18216
|
-
startHeartbeatForChat(row.chat_id);
|
|
18217
|
-
}
|
|
18218
|
-
if (rows.length > 0) {
|
|
18219
|
-
log(`[heartbeat] Started ${rows.length} heartbeat(s)`);
|
|
18220
|
-
}
|
|
18221
|
-
} catch {
|
|
18222
|
-
}
|
|
18223
|
-
}
|
|
18224
|
-
async function runHeartbeatNow(chatId) {
|
|
18225
|
-
const config2 = getHeartbeatConfig(chatId);
|
|
18226
|
-
if (!config2) return;
|
|
18227
|
-
await runHeartbeat(chatId, config2);
|
|
18228
|
-
}
|
|
18229
|
-
async function runHeartbeat(chatId, config2) {
|
|
18230
|
-
if (!isWithinActiveHours(config2.activeStart, config2.activeEnd)) {
|
|
18231
|
-
log(`[heartbeat] Skipping for ${chatId}: outside active hours (${config2.activeStart}-${config2.activeEnd})`);
|
|
18232
|
-
return;
|
|
18233
|
-
}
|
|
18234
|
-
cleanExpiredWatches();
|
|
18235
|
-
const prompt = assembleHeartbeatPrompt(chatId);
|
|
18236
|
-
const resolvedBackend = resolveBackendId(chatId, config2);
|
|
18237
|
-
const resolvedModel = config2.model ?? void 0;
|
|
18238
|
-
try {
|
|
18239
|
-
const response = await askAgent(chatId, prompt, {
|
|
18240
|
-
bootstrapTier: "heartbeat",
|
|
18241
|
-
backend: resolvedBackend,
|
|
18242
|
-
model: resolvedModel,
|
|
18243
|
-
timeoutMs: 12e4
|
|
18244
|
-
});
|
|
18245
|
-
if (response.usage) {
|
|
18246
|
-
const heartbeatModel = resolvedModel ?? getModel(chatId) ?? resolvedBackend;
|
|
18247
|
-
addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, heartbeatModel, void 0, response.usage.contextSize);
|
|
18248
|
-
}
|
|
18249
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18250
|
-
const next = new Date(Date.now() + config2.intervalMs).toISOString();
|
|
18251
|
-
updateHeartbeatTimestamps(chatId, now, next);
|
|
18252
|
-
const trimmed = response.text.trim();
|
|
18253
|
-
if (trimmed === HEARTBEAT_OK || trimmed.startsWith(HEARTBEAT_OK)) {
|
|
18254
|
-
log(`[heartbeat] ${chatId}: nothing to report`);
|
|
18255
|
-
return;
|
|
18256
|
-
}
|
|
18257
|
-
await deliverHeartbeatMessage(chatId, config2, response.text);
|
|
18258
|
-
} catch (err) {
|
|
18259
|
-
error(`[heartbeat] Error for ${chatId}: ${errorMessage(err)}`);
|
|
18260
|
-
const fallbacks = parseHeartbeatFallbacks(config2.fallbacks);
|
|
18261
|
-
if (fallbacks.length > 0) {
|
|
18262
|
-
for (const fb of fallbacks) {
|
|
18263
|
-
try {
|
|
18264
|
-
log(`[heartbeat] Trying fallback: ${fb.backend}${fb.model ? `:${fb.model}` : ""}`);
|
|
18265
|
-
const fbResponse = await askAgent(chatId, prompt, {
|
|
18266
|
-
bootstrapTier: "heartbeat",
|
|
18267
|
-
backend: fb.backend,
|
|
18268
|
-
model: fb.model,
|
|
18269
|
-
timeoutMs: 12e4
|
|
18270
|
-
});
|
|
18271
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18272
|
-
const next = new Date(Date.now() + config2.intervalMs).toISOString();
|
|
18273
|
-
updateHeartbeatTimestamps(chatId, now, next);
|
|
18274
|
-
const trimmed = fbResponse.text.trim();
|
|
18275
|
-
if (trimmed === HEARTBEAT_OK || trimmed.startsWith(HEARTBEAT_OK)) {
|
|
18276
|
-
log(`[heartbeat] ${chatId}: nothing to report (via fallback ${fb.backend})`);
|
|
18277
|
-
return;
|
|
18278
|
-
}
|
|
18279
|
-
await deliverHeartbeatMessage(chatId, config2, fbResponse.text);
|
|
18280
|
-
return;
|
|
18281
|
-
} catch (fbErr) {
|
|
18282
|
-
error(`[heartbeat] Fallback ${fb.backend} failed: ${errorMessage(fbErr)}`);
|
|
18283
|
-
}
|
|
18284
|
-
}
|
|
18285
|
-
}
|
|
18286
|
-
}
|
|
18287
|
-
}
|
|
18288
|
-
async function deliverHeartbeatMessage(chatId, config2, text) {
|
|
18289
|
-
const channelName = config2.channel ?? "telegram";
|
|
18290
|
-
const channel = registry2?.get(channelName);
|
|
18291
|
-
if (!channel) return;
|
|
18292
|
-
const targetChatId = config2.target ?? chatId;
|
|
18293
|
-
if (channelName === "telegram") {
|
|
18294
|
-
const topicMatch = targetChatId.match(/^(.+):topic:(\d+)$/);
|
|
18295
|
-
if (topicMatch) {
|
|
18296
|
-
await channel.sendText(topicMatch[1], text, { threadId: parseInt(topicMatch[2], 10) });
|
|
18297
|
-
} else {
|
|
18298
|
-
await channel.sendText(targetChatId, text);
|
|
18299
|
-
}
|
|
18300
|
-
} else {
|
|
18301
|
-
await channel.sendText(targetChatId, text);
|
|
18302
|
-
}
|
|
18303
|
-
}
|
|
18304
|
-
function resolveBackendId(chatId, config2) {
|
|
18305
|
-
if (config2.backend) {
|
|
18306
|
-
const available = getAvailableBackendIds();
|
|
18307
|
-
if (available.includes(config2.backend)) {
|
|
18308
|
-
return config2.backend;
|
|
18309
|
-
}
|
|
18310
|
-
}
|
|
18311
|
-
return void 0;
|
|
18312
|
-
}
|
|
18313
|
-
function isWithinActiveHours(start, end) {
|
|
18314
|
-
try {
|
|
18315
|
-
const now = /* @__PURE__ */ new Date();
|
|
18316
|
-
const [startH, startM] = start.split(":").map(Number);
|
|
18317
|
-
const [endH, endM] = end.split(":").map(Number);
|
|
18318
|
-
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
|
18319
|
-
const startMinutes = startH * 60 + (startM || 0);
|
|
18320
|
-
const endMinutes = endH * 60 + (endM || 0);
|
|
18321
|
-
if (startMinutes <= endMinutes) {
|
|
18322
|
-
return currentMinutes >= startMinutes && currentMinutes <= endMinutes;
|
|
18323
|
-
}
|
|
18324
|
-
return currentMinutes >= startMinutes || currentMinutes <= endMinutes;
|
|
18325
|
-
} catch {
|
|
18326
|
-
return true;
|
|
18327
|
-
}
|
|
18328
|
-
}
|
|
18329
|
-
function assembleHeartbeatPrompt(chatId) {
|
|
18330
|
-
const sections = [];
|
|
18331
|
-
sections.push("You are running a periodic heartbeat check. Review the following system health data and active watches. If nothing needs the user's attention, respond with exactly HEARTBEAT_OK and nothing else. Only alert on genuine issues \u2014 do NOT report that everything is fine.");
|
|
18332
|
-
const healthSummary = runAllHealthChecks();
|
|
18333
|
-
const healthText = formatHealthForPrompt(healthSummary);
|
|
18334
|
-
sections.push(`[System Health Report]
|
|
18335
|
-
${healthText}`);
|
|
18336
|
-
const watches = getActiveWatches(chatId);
|
|
18337
|
-
if (watches.length > 0) {
|
|
18338
|
-
const watchLines = watches.map((w, i) => {
|
|
18339
|
-
const expiry = w.expiresAt ? ` (expires: ${w.expiresAt})` : "";
|
|
18340
|
-
return `${i + 1}. ${w.description}${expiry}`;
|
|
18341
|
-
});
|
|
18342
|
-
sections.push(`[Active Watches \u2014 execute these checks using your tools]
|
|
18343
|
-
${watchLines.join("\n")}`);
|
|
18344
|
-
}
|
|
18345
|
-
if (existsSync19(HEARTBEAT_MD_PATH)) {
|
|
18346
|
-
try {
|
|
18347
|
-
const custom = readFileSync10(HEARTBEAT_MD_PATH, "utf-8").trim();
|
|
18348
|
-
if (custom) {
|
|
18349
|
-
sections.push(`[Custom checks from HEARTBEAT.md]
|
|
18350
|
-
${custom}`);
|
|
18351
|
-
}
|
|
18352
|
-
} catch {
|
|
18353
|
-
}
|
|
18354
|
-
}
|
|
18355
|
-
return sections.join("\n\n");
|
|
18356
|
-
}
|
|
18357
|
-
function formatHeartbeatStatus(chatId) {
|
|
18358
|
-
const config2 = getHeartbeatConfig(chatId);
|
|
18359
|
-
if (!config2) {
|
|
18360
|
-
return "Heartbeat: not configured\nUse /heartbeat on to enable.";
|
|
18361
|
-
}
|
|
18362
|
-
const watches = getActiveWatches(chatId);
|
|
18363
|
-
const lines = [
|
|
18364
|
-
`Heartbeat: ${config2.enabled ? "ON" : "OFF"}`,
|
|
18365
|
-
`Interval: ${config2.intervalMs / 6e4} minutes`,
|
|
18366
|
-
`Active hours: ${config2.activeStart} - ${config2.activeEnd}`,
|
|
18367
|
-
`Last beat: ${config2.lastBeatAt ?? "never"}`,
|
|
18368
|
-
`Next beat: ${config2.nextBeatAt ?? "N/A"}`
|
|
18369
|
-
];
|
|
18370
|
-
if (config2.backend || config2.model) {
|
|
18371
|
-
lines.push(`Backend: ${config2.backend ?? "default"} | Model: ${config2.model ?? "default"}`);
|
|
18372
|
-
}
|
|
18373
|
-
if (config2.target) {
|
|
18374
|
-
lines.push(`Delivery: ${config2.target}`);
|
|
18375
|
-
}
|
|
18376
|
-
const fallbacks = parseHeartbeatFallbacks(config2.fallbacks);
|
|
18377
|
-
if (fallbacks.length > 0) {
|
|
18378
|
-
lines.push(`Fallbacks: ${fallbacks.map((f) => `${f.backend}${f.model ? `:${f.model}` : ""}`).join(", ")}`);
|
|
18379
|
-
}
|
|
18380
|
-
if (watches.length > 0) {
|
|
18381
|
-
lines.push("", `Active watches (${watches.length}):`);
|
|
18382
|
-
for (const w of watches) {
|
|
18383
|
-
const expiry = w.expiresAt ? ` (until ${w.expiresAt})` : "";
|
|
18384
|
-
lines.push(` - ${w.description}${expiry}`);
|
|
18385
|
-
}
|
|
18386
|
-
}
|
|
18387
|
-
return lines.join("\n");
|
|
18388
|
-
}
|
|
18389
|
-
function parseHeartbeatCommand(chatId, args) {
|
|
18390
|
-
const parts = args.trim().toLowerCase().split(/\s+/);
|
|
18391
|
-
return { action: parts[0] ?? "status", value: parts.slice(1).join(" ") };
|
|
18392
|
-
}
|
|
18393
|
-
var HEARTBEAT_MD_PATH, HEARTBEAT_OK, registry2, activeTimers;
|
|
18394
|
-
var init_heartbeat2 = __esm({
|
|
18395
|
-
"src/bootstrap/heartbeat.ts"() {
|
|
18396
|
-
"use strict";
|
|
18397
|
-
init_paths();
|
|
18398
|
-
init_agent();
|
|
18399
|
-
init_store5();
|
|
18400
|
-
init_backends();
|
|
18401
|
-
init_checks();
|
|
18402
|
-
init_log();
|
|
18403
|
-
HEARTBEAT_MD_PATH = join19(WORKSPACE_PATH, "HEARTBEAT.md");
|
|
18404
|
-
HEARTBEAT_OK = "HEARTBEAT_OK";
|
|
18405
|
-
registry2 = null;
|
|
18406
|
-
activeTimers = /* @__PURE__ */ new Map();
|
|
18407
|
-
}
|
|
18408
|
-
});
|
|
18409
|
-
|
|
18410
18625
|
// src/skills/discover.ts
|
|
18411
18626
|
var discover_exports = {};
|
|
18412
18627
|
__export(discover_exports, {
|
|
@@ -22302,12 +22517,44 @@ async function handleSummarizerCommand(chatId, commandArgs, msg, channel) {
|
|
|
22302
22517
|
[{ label: `Off (disable)${isOff ? " \u2713" : ""}`, data: "summarizer:off", ...isOff ? { style: "primary" } : {} }]
|
|
22303
22518
|
];
|
|
22304
22519
|
for (const a of getAvailableAdapters()) {
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22520
|
+
if (a.id === "ollama") {
|
|
22521
|
+
try {
|
|
22522
|
+
const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
|
|
22523
|
+
const eligible = OllamaService.getEligibleModelsForTask("summarize");
|
|
22524
|
+
for (const modelName of eligible) {
|
|
22525
|
+
const pinned = current.backend === "ollama" && current.model === modelName;
|
|
22526
|
+
buttons.push([{
|
|
22527
|
+
label: `${pinned ? "\u2713 " : ""}Ollama: ${modelName}`,
|
|
22528
|
+
data: `summarizer:ollama:${modelName}`,
|
|
22529
|
+
...pinned ? { style: "primary" } : {}
|
|
22530
|
+
}]);
|
|
22531
|
+
}
|
|
22532
|
+
if (eligible.length === 0 && a.summarizerModel) {
|
|
22533
|
+
const pinned = current.backend === a.id;
|
|
22534
|
+
buttons.push([{
|
|
22535
|
+
label: `${pinned ? "\u2713 " : ""}${a.displayName}: ${a.summarizerModel}`,
|
|
22536
|
+
data: `summarizer:${a.id}:${a.summarizerModel}`,
|
|
22537
|
+
...pinned ? { style: "primary" } : {}
|
|
22538
|
+
}]);
|
|
22539
|
+
}
|
|
22540
|
+
} catch {
|
|
22541
|
+
if (a.summarizerModel) {
|
|
22542
|
+
const pinned = current.backend === a.id;
|
|
22543
|
+
buttons.push([{
|
|
22544
|
+
label: `${pinned ? "\u2713 " : ""}${a.displayName}: ${a.summarizerModel}`,
|
|
22545
|
+
data: `summarizer:${a.id}:${a.summarizerModel}`,
|
|
22546
|
+
...pinned ? { style: "primary" } : {}
|
|
22547
|
+
}]);
|
|
22548
|
+
}
|
|
22549
|
+
}
|
|
22550
|
+
} else {
|
|
22551
|
+
const pinned = current.backend === a.id;
|
|
22552
|
+
buttons.push([{
|
|
22553
|
+
label: `${pinned ? "\u2713 " : ""}${a.displayName}: ${a.summarizerModel}`,
|
|
22554
|
+
data: `summarizer:${a.id}:${a.summarizerModel}`,
|
|
22555
|
+
...pinned ? { style: "primary" } : {}
|
|
22556
|
+
}]);
|
|
22557
|
+
}
|
|
22311
22558
|
}
|
|
22312
22559
|
await channel.sendKeyboard(chatId, `Session summarizer (current: ${currentLabel}):`, buttons);
|
|
22313
22560
|
} else {
|
|
@@ -23275,7 +23522,21 @@ function parseExtractedSkill(llmResponse) {
|
|
|
23275
23522
|
let content = llmResponse;
|
|
23276
23523
|
const fenceMatch = llmResponse.match(/```(?:markdown|md)?\s*\n([\s\S]*?)```/);
|
|
23277
23524
|
if (fenceMatch) content = fenceMatch[1].trim();
|
|
23278
|
-
|
|
23525
|
+
let fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
23526
|
+
if (!fmMatch) {
|
|
23527
|
+
const greedyFence = llmResponse.match(/```(?:markdown|md)?\s*\n([\s\S]*)```/);
|
|
23528
|
+
if (greedyFence) {
|
|
23529
|
+
content = greedyFence[1].trim();
|
|
23530
|
+
fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
23531
|
+
}
|
|
23532
|
+
}
|
|
23533
|
+
if (!fmMatch) {
|
|
23534
|
+
fmMatch = llmResponse.match(/---\s*\n([\s\S]*?)\n---/);
|
|
23535
|
+
if (fmMatch) {
|
|
23536
|
+
const fmStart = llmResponse.indexOf(fmMatch[0]);
|
|
23537
|
+
content = llmResponse.slice(fmStart).replace(/```\s*$/, "").trim();
|
|
23538
|
+
}
|
|
23539
|
+
}
|
|
23279
23540
|
if (!fmMatch) {
|
|
23280
23541
|
warn("[auto-skill] No frontmatter found in extracted skill");
|
|
23281
23542
|
return null;
|
|
@@ -24027,8 +24288,8 @@ ${rotationNote}`, { parseMode: "html" });
|
|
|
24027
24288
|
if (action === "toggle") {
|
|
24028
24289
|
const backend2 = parts[2];
|
|
24029
24290
|
const model2 = parts[3];
|
|
24030
|
-
const { getAdapter:
|
|
24031
|
-
const toggleAdapter =
|
|
24291
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
24292
|
+
const toggleAdapter = getAdapter4(backend2);
|
|
24032
24293
|
const label2 = toggleAdapter.availableModels[model2]?.label ?? model2;
|
|
24033
24294
|
const { toggleParticipant: toggleParticipant2, buildSelectKeyboard: buildSelectKeyboard2, hasPendingCouncil: hasPendingCouncil2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports));
|
|
24034
24295
|
if (!hasPendingCouncil2(chatId)) {
|
|
@@ -24274,54 +24535,38 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
|
|
|
24274
24535
|
}
|
|
24275
24536
|
return;
|
|
24276
24537
|
} else if (data.startsWith("hb:")) {
|
|
24538
|
+
const { enableHeartbeat: enableHeartbeat2, disableHeartbeat: disableHeartbeat2, runHeartbeatNow: triggerHb, updateHeartbeatConfig: updateHeartbeatConfig2, findHeartbeatJob: findHeartbeatJob2 } = await Promise.resolve().then(() => (init_heartbeat2(), heartbeat_exports));
|
|
24277
24539
|
const rest = data.slice(3);
|
|
24278
24540
|
if (rest === "on") {
|
|
24279
|
-
|
|
24280
|
-
startHeartbeatForChat(chatId);
|
|
24541
|
+
enableHeartbeat2(chatId);
|
|
24281
24542
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24282
24543
|
} else if (rest === "off") {
|
|
24283
|
-
|
|
24284
|
-
|
|
24285
|
-
await channel.sendText(chatId, "\u26A0\uFE0F Heartbeat disabled. You won't receive proactive health alerts or watch notifications.\nRe-enable with /heartbeat on.", { parseMode: "plain" });
|
|
24544
|
+
disableHeartbeat2();
|
|
24545
|
+
await channel.sendText(chatId, "\u26A0\uFE0F Heartbeat paused. Re-enable with /heartbeat on.", { parseMode: "plain" });
|
|
24286
24546
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24287
24547
|
} else if (rest.startsWith("interval:")) {
|
|
24288
24548
|
const min = parseInt(rest.slice(9), 10);
|
|
24289
24549
|
if (isNaN(min) || min < 1) return;
|
|
24290
|
-
|
|
24291
|
-
setHeartbeatConfig(chatId, { intervalMs: ms });
|
|
24292
|
-
stopHeartbeatForChat(chatId);
|
|
24293
|
-
const hbConf = getHeartbeatConfig(chatId);
|
|
24294
|
-
if (hbConf?.enabled) startHeartbeatForChat(chatId);
|
|
24550
|
+
updateHeartbeatConfig2({ intervalMs: min * 6e4 });
|
|
24295
24551
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24296
24552
|
} else if (rest.startsWith("backend:")) {
|
|
24297
24553
|
const bid = rest.slice(8);
|
|
24298
24554
|
if (bid === "default") {
|
|
24299
|
-
|
|
24300
|
-
updateHeartbeatField(chatId, "model", null);
|
|
24555
|
+
updateHeartbeatConfig2({ backend: null, model: null });
|
|
24301
24556
|
} else {
|
|
24302
|
-
|
|
24303
|
-
updateHeartbeatField(chatId, "model", null);
|
|
24557
|
+
updateHeartbeatConfig2({ backend: bid, model: null });
|
|
24304
24558
|
}
|
|
24305
24559
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24306
24560
|
} else if (rest.startsWith("model:")) {
|
|
24307
24561
|
const model2 = rest.slice(6);
|
|
24308
|
-
|
|
24309
|
-
updateHeartbeatField(chatId, "model", null);
|
|
24310
|
-
} else {
|
|
24311
|
-
updateHeartbeatField(chatId, "model", model2);
|
|
24312
|
-
}
|
|
24562
|
+
updateHeartbeatConfig2({ model: model2 === "default" ? null : model2 });
|
|
24313
24563
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24314
24564
|
} else if (rest === "thinking") {
|
|
24315
|
-
|
|
24316
|
-
const current = config2?.thinking ?? "off";
|
|
24317
|
-
const cycle = ["off", "low", "medium", "high"];
|
|
24318
|
-
const next = cycle[(cycle.indexOf(current) + 1) % cycle.length];
|
|
24319
|
-
updateHeartbeatField(chatId, "thinking", next === "off" ? null : next);
|
|
24320
|
-
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24565
|
+
await channel.sendText(chatId, "Thinking level for heartbeat: use /editjob to configure.", { parseMode: "plain" });
|
|
24321
24566
|
} else if (rest === "run") {
|
|
24322
24567
|
await channel.sendText(chatId, "\u23F3 Running heartbeat check...", { parseMode: "plain" });
|
|
24323
24568
|
try {
|
|
24324
|
-
await
|
|
24569
|
+
await triggerHb();
|
|
24325
24570
|
await channel.sendText(chatId, "\u2705 Heartbeat check complete.", { parseMode: "plain" });
|
|
24326
24571
|
} catch (err) {
|
|
24327
24572
|
await channel.sendText(chatId, `\u274C Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, { parseMode: "plain" });
|
|
@@ -24571,7 +24816,6 @@ var init_callbacks = __esm({
|
|
|
24571
24816
|
init_discover();
|
|
24572
24817
|
init_profile();
|
|
24573
24818
|
init_profile();
|
|
24574
|
-
init_heartbeat2();
|
|
24575
24819
|
init_store5();
|
|
24576
24820
|
init_summarize();
|
|
24577
24821
|
init_inject();
|
|
@@ -25748,7 +25992,7 @@ function startSingleJob(job) {
|
|
|
25748
25992
|
}
|
|
25749
25993
|
}
|
|
25750
25994
|
function stopJobTimer(id) {
|
|
25751
|
-
const timer =
|
|
25995
|
+
const timer = activeTimers.get(id);
|
|
25752
25996
|
if (!timer) return;
|
|
25753
25997
|
if (timer instanceof Cron) {
|
|
25754
25998
|
timer.stop();
|
|
@@ -25756,7 +26000,7 @@ function stopJobTimer(id) {
|
|
|
25756
26000
|
clearTimeout(timer);
|
|
25757
26001
|
clearInterval(timer);
|
|
25758
26002
|
}
|
|
25759
|
-
|
|
26003
|
+
activeTimers.delete(id);
|
|
25760
26004
|
}
|
|
25761
26005
|
function cancelJob(id) {
|
|
25762
26006
|
stopJobTimer(id);
|
|
@@ -25803,7 +26047,7 @@ function startCronJob(job) {
|
|
|
25803
26047
|
await executeJob(fresh);
|
|
25804
26048
|
}
|
|
25805
26049
|
});
|
|
25806
|
-
|
|
26050
|
+
activeTimers.set(job.id, cronInstance);
|
|
25807
26051
|
const nextRun = cronInstance.nextRun();
|
|
25808
26052
|
if (nextRun) {
|
|
25809
26053
|
updateJobNextRun(job.id, nextRun.toISOString());
|
|
@@ -25824,9 +26068,9 @@ function startOneShot(job) {
|
|
|
25824
26068
|
await executeJob(fresh);
|
|
25825
26069
|
}
|
|
25826
26070
|
updateJobEnabled(jobId, false);
|
|
25827
|
-
|
|
26071
|
+
activeTimers.delete(jobId);
|
|
25828
26072
|
}, Math.max(0, delay));
|
|
25829
|
-
|
|
26073
|
+
activeTimers.set(job.id, timer);
|
|
25830
26074
|
updateJobNextRun(job.id, job.atTime);
|
|
25831
26075
|
}
|
|
25832
26076
|
function startInterval(job) {
|
|
@@ -25839,7 +26083,7 @@ function startInterval(job) {
|
|
|
25839
26083
|
updateJobNextRun(jobId, new Date(Date.now() + everyMs).toISOString());
|
|
25840
26084
|
}
|
|
25841
26085
|
}, everyMs);
|
|
25842
|
-
|
|
26086
|
+
activeTimers.set(job.id, interval);
|
|
25843
26087
|
updateJobNextRun(job.id, new Date(Date.now() + everyMs).toISOString());
|
|
25844
26088
|
}
|
|
25845
26089
|
async function executeJob(job) {
|
|
@@ -25850,7 +26094,7 @@ async function executeJob(job) {
|
|
|
25850
26094
|
return;
|
|
25851
26095
|
}
|
|
25852
26096
|
runningJobs.add(job.id);
|
|
25853
|
-
const timer =
|
|
26097
|
+
const timer = activeTimers.get(job.id);
|
|
25854
26098
|
if (timer instanceof Cron) {
|
|
25855
26099
|
const nextRun = timer.nextRun();
|
|
25856
26100
|
if (nextRun) updateJobNextRun(job.id, nextRun.toISOString());
|
|
@@ -25944,6 +26188,24 @@ async function runWithRetry(job, model2, runId, t0) {
|
|
|
25944
26188
|
}
|
|
25945
26189
|
return { text: formatNightlySummary2(allInsights, totalPending) };
|
|
25946
26190
|
}
|
|
26191
|
+
if (job.jobType === "heartbeat") {
|
|
26192
|
+
const { assembleHeartbeatPrompt: assembleHeartbeatPrompt2, HEARTBEAT_OK: HEARTBEAT_OK2 } = await Promise.resolve().then(() => (init_heartbeat2(), heartbeat_exports));
|
|
26193
|
+
const hbPrompt = assembleHeartbeatPrompt2(job.chatId);
|
|
26194
|
+
const hbTimeoutMs = job.timeout ? job.timeout * 1e3 : 12e4;
|
|
26195
|
+
const response = await askAgent(chatId, hbPrompt, {
|
|
26196
|
+
bootstrapTier: "heartbeat",
|
|
26197
|
+
backend: resolveJobBackendId(job),
|
|
26198
|
+
model: model2,
|
|
26199
|
+
timeoutMs: hbTimeoutMs
|
|
26200
|
+
});
|
|
26201
|
+
const trimmed = response.text.trim();
|
|
26202
|
+
if (trimmed === HEARTBEAT_OK2 || trimmed.startsWith(HEARTBEAT_OK2)) {
|
|
26203
|
+
log(`[heartbeat] Job #${job.id}: nothing to report`);
|
|
26204
|
+
return { text: "", usage: response.usage };
|
|
26205
|
+
}
|
|
26206
|
+
const cleanText = response.text.replace(/\[REACT:[^\]]*\]/g, "").replace(/\[SEND_FILE:[^\]]*\]/g, "").replace(/\[GENERATE_IMAGE:[^\]]*\]/g, "").replace(/\[HISTORY_SEARCH:[^\]]*\]/g, "").replace(/\[UPDATE_USER:[^\]]*\]/g, "").trim();
|
|
26207
|
+
return { text: cleanText, usage: response.usage };
|
|
26208
|
+
}
|
|
25947
26209
|
if (job.thinking && job.thinking !== "auto") {
|
|
25948
26210
|
setThinkingLevel(chatId, job.thinking);
|
|
25949
26211
|
}
|
|
@@ -26037,13 +26299,13 @@ function resolveJobModel(job) {
|
|
|
26037
26299
|
}
|
|
26038
26300
|
}
|
|
26039
26301
|
function shutdownScheduler() {
|
|
26040
|
-
for (const [id] of
|
|
26302
|
+
for (const [id] of activeTimers) {
|
|
26041
26303
|
stopJobTimer(id);
|
|
26042
26304
|
}
|
|
26043
26305
|
stopHealthMonitor2();
|
|
26044
26306
|
log("[scheduler] Shutdown complete");
|
|
26045
26307
|
}
|
|
26046
|
-
var
|
|
26308
|
+
var activeTimers, runningJobs;
|
|
26047
26309
|
var init_cron = __esm({
|
|
26048
26310
|
"src/scheduler/cron.ts"() {
|
|
26049
26311
|
"use strict";
|
|
@@ -26054,7 +26316,7 @@ var init_cron = __esm({
|
|
|
26054
26316
|
init_delivery();
|
|
26055
26317
|
init_retry();
|
|
26056
26318
|
init_health2();
|
|
26057
|
-
|
|
26319
|
+
activeTimers = /* @__PURE__ */ new Map();
|
|
26058
26320
|
runningJobs = /* @__PURE__ */ new Set();
|
|
26059
26321
|
}
|
|
26060
26322
|
});
|
|
@@ -26200,6 +26462,16 @@ var init_wrap_backend = __esm({
|
|
|
26200
26462
|
maxConcurrentSessions: 4,
|
|
26201
26463
|
specialties: ["multi-provider", "code-generation", "analysis", "planning", "debugging"],
|
|
26202
26464
|
mcpInjection: "config-file"
|
|
26465
|
+
},
|
|
26466
|
+
ollama: {
|
|
26467
|
+
supportsSessionResume: false,
|
|
26468
|
+
supportsMcp: false,
|
|
26469
|
+
// HTTP-based adapter — no CLI MCP injection
|
|
26470
|
+
supportsNdjson: false,
|
|
26471
|
+
supportsPermissionModes: false,
|
|
26472
|
+
maxConcurrentSessions: 8,
|
|
26473
|
+
specialties: ["local-inference", "summarization"],
|
|
26474
|
+
mcpInjection: "add-remove"
|
|
26203
26475
|
}
|
|
26204
26476
|
};
|
|
26205
26477
|
}
|
|
@@ -26729,52 +27001,7 @@ ${body.replace(/<[^>]*>/g, "").trim()}</code>
|
|
|
26729
27001
|
});
|
|
26730
27002
|
|
|
26731
27003
|
// src/channels/telegram.ts
|
|
26732
|
-
import { API_CONSTANTS, Bot, GrammyError, InlineKeyboard, InputFile } from "grammy";
|
|
26733
|
-
function tripCircuitBreaker(retrySec) {
|
|
26734
|
-
const until = Date.now() + retrySec * 1e3;
|
|
26735
|
-
if (until > circuitBreakerUntil) {
|
|
26736
|
-
circuitBreakerUntil = until;
|
|
26737
|
-
warn(`[telegram] Circuit breaker tripped \u2014 blocking ALL Telegram API calls for ${retrySec}s`);
|
|
26738
|
-
}
|
|
26739
|
-
}
|
|
26740
|
-
function isCircuitBreakerActive() {
|
|
26741
|
-
return Date.now() < circuitBreakerUntil;
|
|
26742
|
-
}
|
|
26743
|
-
function isRateLimitError(err) {
|
|
26744
|
-
return err instanceof GrammyError && err.error_code === 429;
|
|
26745
|
-
}
|
|
26746
|
-
async function withRetry(label2, fn) {
|
|
26747
|
-
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
26748
|
-
if (isCircuitBreakerActive()) {
|
|
26749
|
-
throw new GrammyError(
|
|
26750
|
-
`Circuit breaker active \u2014 skipping ${label2}`,
|
|
26751
|
-
{ ok: false, error_code: 429, description: "Rate limited (circuit breaker)" },
|
|
26752
|
-
label2,
|
|
26753
|
-
{}
|
|
26754
|
-
);
|
|
26755
|
-
}
|
|
26756
|
-
try {
|
|
26757
|
-
return await fn();
|
|
26758
|
-
} catch (err) {
|
|
26759
|
-
if (err instanceof GrammyError && err.error_code === 429) {
|
|
26760
|
-
const retrySec = err.parameters?.retry_after ?? FALLBACK_RETRY_SEC;
|
|
26761
|
-
tripCircuitBreaker(retrySec);
|
|
26762
|
-
if (retrySec > GIVE_UP_THRESHOLD_SEC) {
|
|
26763
|
-
warn(`[telegram] 429 on ${label2} \u2014 retry_after ${retrySec}s exceeds threshold, giving up`);
|
|
26764
|
-
throw err;
|
|
26765
|
-
}
|
|
26766
|
-
if (attempt < MAX_RETRIES2) {
|
|
26767
|
-
warn(`[telegram] 429 on ${label2} (attempt ${attempt + 1}/${MAX_RETRIES2}) \u2014 retrying in ${retrySec}s`);
|
|
26768
|
-
await new Promise((r) => setTimeout(r, retrySec * 1e3));
|
|
26769
|
-
continue;
|
|
26770
|
-
}
|
|
26771
|
-
warn(`[telegram] 429 on ${label2} \u2014 exhausted ${MAX_RETRIES2} retries, giving up`);
|
|
26772
|
-
}
|
|
26773
|
-
throw err;
|
|
26774
|
-
}
|
|
26775
|
-
}
|
|
26776
|
-
throw new Error(`withRetry: unreachable`);
|
|
26777
|
-
}
|
|
27004
|
+
import { API_CONSTANTS, Bot, GrammyError as GrammyError2, InlineKeyboard, InputFile } from "grammy";
|
|
26778
27005
|
function isFastPathMessage(msg) {
|
|
26779
27006
|
if (msg.type === "command" && msg.command && FAST_PATH_COMMANDS.has(msg.command)) {
|
|
26780
27007
|
return true;
|
|
@@ -26797,17 +27024,14 @@ function numericChatId(chatId) {
|
|
|
26797
27024
|
const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
|
|
26798
27025
|
return parseInt(raw);
|
|
26799
27026
|
}
|
|
26800
|
-
var
|
|
27027
|
+
var FAST_PATH_COMMANDS, TelegramChannel;
|
|
26801
27028
|
var init_telegram2 = __esm({
|
|
26802
27029
|
"src/channels/telegram.ts"() {
|
|
26803
27030
|
"use strict";
|
|
26804
27031
|
init_telegram();
|
|
26805
27032
|
init_log();
|
|
26806
27033
|
init_store5();
|
|
26807
|
-
|
|
26808
|
-
FALLBACK_RETRY_SEC = 3;
|
|
26809
|
-
GIVE_UP_THRESHOLD_SEC = 30;
|
|
26810
|
-
circuitBreakerUntil = 0;
|
|
27034
|
+
init_telegram_throttle();
|
|
26811
27035
|
FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
|
|
26812
27036
|
TelegramChannel = class {
|
|
26813
27037
|
name = "telegram";
|
|
@@ -26818,6 +27042,7 @@ var init_telegram2 = __esm({
|
|
|
26818
27042
|
agentMessageIds = /* @__PURE__ */ new Map();
|
|
26819
27043
|
// messageId → chatId
|
|
26820
27044
|
reactionHandlers = [];
|
|
27045
|
+
throttle;
|
|
26821
27046
|
constructor() {
|
|
26822
27047
|
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
26823
27048
|
if (!token) {
|
|
@@ -26833,6 +27058,17 @@ var init_telegram2 = __esm({
|
|
|
26833
27058
|
this.primaryChatId = ids[0];
|
|
26834
27059
|
this.allowedChatIds = new Set(ids);
|
|
26835
27060
|
this.bot = new Bot(token);
|
|
27061
|
+
this.throttle = new TelegramThrottle();
|
|
27062
|
+
this.throttle.setResumeNotifier(async (chatId, pausedSec, queuedCount) => {
|
|
27063
|
+
try {
|
|
27064
|
+
await this.bot.api.sendMessage(
|
|
27065
|
+
numericChatId(chatId),
|
|
27066
|
+
`\u26A0\uFE0F Rate-limited by Telegram for ${pausedSec}s \u2014 delivering ${queuedCount} pending message(s) now.`
|
|
27067
|
+
);
|
|
27068
|
+
} catch (err) {
|
|
27069
|
+
warn("[telegram] Resume notification failed:", err instanceof Error ? err.message : err);
|
|
27070
|
+
}
|
|
27071
|
+
});
|
|
26836
27072
|
}
|
|
26837
27073
|
/** The first ID in ALLOWED_CHAT_ID — used for heartbeat, notifications, etc. */
|
|
26838
27074
|
getPrimaryChatId() {
|
|
@@ -26968,11 +27204,17 @@ var init_telegram2 = __esm({
|
|
|
26968
27204
|
}
|
|
26969
27205
|
const data = ctx.callbackQuery.data;
|
|
26970
27206
|
const messageId = ctx.callbackQuery.message?.message_id?.toString();
|
|
27207
|
+
const threadId = ctx.callbackQuery.message?.message_thread_id;
|
|
26971
27208
|
ctx.answerCallbackQuery().catch(() => {
|
|
26972
27209
|
});
|
|
26973
27210
|
(async () => {
|
|
27211
|
+
let ch = this;
|
|
27212
|
+
if (threadId) {
|
|
27213
|
+
const { withThread: withThread2 } = await Promise.resolve().then(() => (init_thread_wrapper(), thread_wrapper_exports));
|
|
27214
|
+
ch = withThread2(this, threadId);
|
|
27215
|
+
}
|
|
26974
27216
|
for (const handler2 of this.callbackHandlers) {
|
|
26975
|
-
await handler2(chatId, data,
|
|
27217
|
+
await handler2(chatId, data, ch, messageId);
|
|
26976
27218
|
}
|
|
26977
27219
|
})().catch((err) => {
|
|
26978
27220
|
error("[telegram] Callback handler error:", err);
|
|
@@ -27002,7 +27244,7 @@ var init_telegram2 = __esm({
|
|
|
27002
27244
|
});
|
|
27003
27245
|
this.bot.catch((botError) => {
|
|
27004
27246
|
const err = botError.error;
|
|
27005
|
-
if (err instanceof
|
|
27247
|
+
if (err instanceof GrammyError2) {
|
|
27006
27248
|
error(`[telegram] Grammy error ${err.error_code}: ${err.description}`);
|
|
27007
27249
|
} else {
|
|
27008
27250
|
error("[telegram] Unhandled error:", err);
|
|
@@ -27022,6 +27264,7 @@ var init_telegram2 = __esm({
|
|
|
27022
27264
|
await this.bot.stop();
|
|
27023
27265
|
}
|
|
27024
27266
|
async sendTyping(chatId, threadId) {
|
|
27267
|
+
if (this.throttle.isPaused()) return;
|
|
27025
27268
|
try {
|
|
27026
27269
|
await this.bot.api.sendChatAction(numericChatId(chatId), "typing", {
|
|
27027
27270
|
...threadId ? { message_thread_id: threadId } : {}
|
|
@@ -27036,7 +27279,8 @@ var init_telegram2 = __esm({
|
|
|
27036
27279
|
if (parseMode === "plain") {
|
|
27037
27280
|
const plainChunks = splitMessage(sanitizeForTelegram(text));
|
|
27038
27281
|
for (const chunk of plainChunks) {
|
|
27039
|
-
const sent = await
|
|
27282
|
+
const sent = await this.throttle.send(
|
|
27283
|
+
chatId,
|
|
27040
27284
|
"sendText:plain",
|
|
27041
27285
|
() => this.bot.api.sendMessage(numericChatId(chatId), chunk, { ...threadOpts, ...replyOpts })
|
|
27042
27286
|
);
|
|
@@ -27048,7 +27292,8 @@ var init_telegram2 = __esm({
|
|
|
27048
27292
|
const chunks = splitMessage(formatted);
|
|
27049
27293
|
for (const chunk of chunks) {
|
|
27050
27294
|
try {
|
|
27051
|
-
const sent = await
|
|
27295
|
+
const sent = await this.throttle.send(
|
|
27296
|
+
chatId,
|
|
27052
27297
|
"sendText:html",
|
|
27053
27298
|
() => this.bot.api.sendMessage(numericChatId(chatId), chunk, {
|
|
27054
27299
|
parse_mode: "HTML",
|
|
@@ -27058,9 +27303,9 @@ var init_telegram2 = __esm({
|
|
|
27058
27303
|
);
|
|
27059
27304
|
this.trackAgentMessage(sent.message_id, chatId);
|
|
27060
27305
|
} catch (err) {
|
|
27061
|
-
if (isRateLimitError(err)) throw err;
|
|
27062
27306
|
warn("[telegram] sendText HTML failed, falling back to plain:", err instanceof Error ? err.message : err);
|
|
27063
|
-
const sent = await
|
|
27307
|
+
const sent = await this.throttle.send(
|
|
27308
|
+
chatId,
|
|
27064
27309
|
"sendText:fallback",
|
|
27065
27310
|
() => this.bot.api.sendMessage(
|
|
27066
27311
|
numericChatId(chatId),
|
|
@@ -27073,7 +27318,8 @@ var init_telegram2 = __esm({
|
|
|
27073
27318
|
}
|
|
27074
27319
|
}
|
|
27075
27320
|
async sendVoice(chatId, audioBuffer, fileName, threadId) {
|
|
27076
|
-
await
|
|
27321
|
+
await this.throttle.send(
|
|
27322
|
+
chatId,
|
|
27077
27323
|
"sendVoice",
|
|
27078
27324
|
() => this.bot.api.sendVoice(
|
|
27079
27325
|
numericChatId(chatId),
|
|
@@ -27083,7 +27329,8 @@ var init_telegram2 = __esm({
|
|
|
27083
27329
|
);
|
|
27084
27330
|
}
|
|
27085
27331
|
async sendFile(chatId, buffer, fileName, _mimeType, threadId) {
|
|
27086
|
-
await
|
|
27332
|
+
await this.throttle.send(
|
|
27333
|
+
chatId,
|
|
27087
27334
|
"sendFile",
|
|
27088
27335
|
() => this.bot.api.sendDocument(
|
|
27089
27336
|
numericChatId(chatId),
|
|
@@ -27103,7 +27350,8 @@ var init_telegram2 = __esm({
|
|
|
27103
27350
|
const formatted = sanitizeForTelegram(parseMode === "html" ? text : parseMode === "plain" ? text : formatForTelegram(text));
|
|
27104
27351
|
const threadOpts = threadId ? { message_thread_id: threadId } : {};
|
|
27105
27352
|
const opts = parseMode === "plain" ? { ...threadOpts } : { parse_mode: "HTML", ...threadOpts };
|
|
27106
|
-
const msg = await
|
|
27353
|
+
const msg = await this.throttle.send(
|
|
27354
|
+
chatId,
|
|
27107
27355
|
"sendTextReturningId",
|
|
27108
27356
|
() => this.bot.api.sendMessage(numericChatId(chatId), formatted, opts)
|
|
27109
27357
|
);
|
|
@@ -27116,7 +27364,8 @@ var init_telegram2 = __esm({
|
|
|
27116
27364
|
async editText(chatId, messageId, text, parseMode) {
|
|
27117
27365
|
const formatted = sanitizeForTelegram(parseMode === "html" ? text : formatForTelegram(text));
|
|
27118
27366
|
try {
|
|
27119
|
-
await
|
|
27367
|
+
await this.throttle.send(
|
|
27368
|
+
chatId,
|
|
27120
27369
|
"editText:html",
|
|
27121
27370
|
() => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
|
|
27122
27371
|
parse_mode: "HTML"
|
|
@@ -27124,10 +27373,10 @@ var init_telegram2 = __esm({
|
|
|
27124
27373
|
);
|
|
27125
27374
|
return true;
|
|
27126
27375
|
} catch (err) {
|
|
27127
|
-
if (isRateLimitError(err)) return false;
|
|
27128
27376
|
warn("[telegram] editText HTML failed, trying plain fallback:", err instanceof Error ? err.message : err);
|
|
27129
27377
|
try {
|
|
27130
|
-
await
|
|
27378
|
+
await this.throttle.send(
|
|
27379
|
+
chatId,
|
|
27131
27380
|
"editText:fallback",
|
|
27132
27381
|
() => this.bot.api.editMessageText(
|
|
27133
27382
|
numericChatId(chatId),
|
|
@@ -27155,7 +27404,8 @@ var init_telegram2 = __esm({
|
|
|
27155
27404
|
}
|
|
27156
27405
|
const formatted = sanitizeForTelegram(formatForTelegram(text));
|
|
27157
27406
|
try {
|
|
27158
|
-
await
|
|
27407
|
+
await this.throttle.send(
|
|
27408
|
+
chatId,
|
|
27159
27409
|
"editKeyboard:html",
|
|
27160
27410
|
() => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
|
|
27161
27411
|
parse_mode: "HTML",
|
|
@@ -27164,9 +27414,9 @@ var init_telegram2 = __esm({
|
|
|
27164
27414
|
);
|
|
27165
27415
|
return true;
|
|
27166
27416
|
} catch (err) {
|
|
27167
|
-
if (isRateLimitError(err)) return false;
|
|
27168
27417
|
try {
|
|
27169
|
-
await
|
|
27418
|
+
await this.throttle.send(
|
|
27419
|
+
chatId,
|
|
27170
27420
|
"editKeyboard:plain",
|
|
27171
27421
|
() => this.bot.api.editMessageText(
|
|
27172
27422
|
numericChatId(chatId),
|
|
@@ -27206,7 +27456,8 @@ var init_telegram2 = __esm({
|
|
|
27206
27456
|
const formatted = sanitizeForTelegram(formatForTelegram(safeText));
|
|
27207
27457
|
const threadOpts = threadId ? { message_thread_id: threadId } : {};
|
|
27208
27458
|
try {
|
|
27209
|
-
const msg = await
|
|
27459
|
+
const msg = await this.throttle.send(
|
|
27460
|
+
chatId,
|
|
27210
27461
|
"sendKeyboard",
|
|
27211
27462
|
() => this.bot.api.sendMessage(numericChatId(chatId), formatted, {
|
|
27212
27463
|
parse_mode: "HTML",
|
|
@@ -27216,14 +27467,11 @@ var init_telegram2 = __esm({
|
|
|
27216
27467
|
);
|
|
27217
27468
|
return msg.message_id.toString();
|
|
27218
27469
|
} catch (err) {
|
|
27219
|
-
if (isRateLimitError(err)) {
|
|
27220
|
-
warn(`[telegram] sendKeyboard rate-limited, skipping all fallbacks`);
|
|
27221
|
-
return void 0;
|
|
27222
|
-
}
|
|
27223
27470
|
error(`[telegram] sendKeyboard failed (chat=${chatId}, textLen=${text.length}):`, err);
|
|
27224
27471
|
try {
|
|
27225
27472
|
const escaped = safeText.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
27226
|
-
const retryMsg = await
|
|
27473
|
+
const retryMsg = await this.throttle.send(
|
|
27474
|
+
chatId,
|
|
27227
27475
|
"sendKeyboard:plain",
|
|
27228
27476
|
() => this.bot.api.sendMessage(numericChatId(chatId), escaped, {
|
|
27229
27477
|
reply_markup: keyboard,
|
|
@@ -27232,13 +27480,13 @@ var init_telegram2 = __esm({
|
|
|
27232
27480
|
);
|
|
27233
27481
|
return retryMsg.message_id.toString();
|
|
27234
27482
|
} catch (plainErr) {
|
|
27235
|
-
if (isRateLimitError(plainErr)) return void 0;
|
|
27236
27483
|
error(`[telegram] sendKeyboard plain retry also failed:`, plainErr);
|
|
27237
27484
|
}
|
|
27238
27485
|
try {
|
|
27239
27486
|
const plainText = safeText.replace(/<[^>]+>/g, "");
|
|
27240
27487
|
if (plainText.trim()) {
|
|
27241
|
-
await
|
|
27488
|
+
await this.throttle.send(
|
|
27489
|
+
chatId,
|
|
27242
27490
|
"sendKeyboard:text-rescue",
|
|
27243
27491
|
() => this.bot.api.sendMessage(numericChatId(chatId), plainText, { ...threadOpts })
|
|
27244
27492
|
);
|
|
@@ -27246,7 +27494,8 @@ var init_telegram2 = __esm({
|
|
|
27246
27494
|
} catch {
|
|
27247
27495
|
}
|
|
27248
27496
|
try {
|
|
27249
|
-
const fallbackMsg = await
|
|
27497
|
+
const fallbackMsg = await this.throttle.send(
|
|
27498
|
+
chatId,
|
|
27250
27499
|
"sendKeyboard:fallback",
|
|
27251
27500
|
() => this.bot.api.sendMessage(numericChatId(chatId), "\u2B06\uFE0F (see above for details)", {
|
|
27252
27501
|
reply_markup: keyboard,
|
|
@@ -27580,7 +27829,7 @@ var init_bootstrap = __esm({
|
|
|
27580
27829
|
});
|
|
27581
27830
|
|
|
27582
27831
|
// src/memory/migrate-embeddings.ts
|
|
27583
|
-
function
|
|
27832
|
+
function sleep2(ms) {
|
|
27584
27833
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
27585
27834
|
}
|
|
27586
27835
|
async function migrateEmbeddings() {
|
|
@@ -27607,7 +27856,7 @@ async function migrateEmbeddings() {
|
|
|
27607
27856
|
}
|
|
27608
27857
|
}
|
|
27609
27858
|
log(`[migrate-embeddings] Embedded ${memoriesEmbedded} memories so far...`);
|
|
27610
|
-
if (batch.length === BATCH_SIZE) await
|
|
27859
|
+
if (batch.length === BATCH_SIZE) await sleep2(BATCH_DELAY_MS);
|
|
27611
27860
|
}
|
|
27612
27861
|
while (true) {
|
|
27613
27862
|
const batch = getSessionSummariesWithoutEmbeddings(BATCH_SIZE);
|
|
@@ -27625,7 +27874,7 @@ async function migrateEmbeddings() {
|
|
|
27625
27874
|
}
|
|
27626
27875
|
}
|
|
27627
27876
|
log(`[migrate-embeddings] Embedded ${sessionsEmbedded} session summaries so far...`);
|
|
27628
|
-
if (batch.length === BATCH_SIZE) await
|
|
27877
|
+
if (batch.length === BATCH_SIZE) await sleep2(BATCH_DELAY_MS);
|
|
27629
27878
|
}
|
|
27630
27879
|
log(`[migrate-embeddings] Migration complete: ${memoriesEmbedded} memories, ${sessionsEmbedded} session summaries`);
|
|
27631
27880
|
return { memories: memoriesEmbedded, sessions: sessionsEmbedded };
|
|
@@ -28334,11 +28583,11 @@ async function main() {
|
|
|
28334
28583
|
} catch {
|
|
28335
28584
|
}
|
|
28336
28585
|
try {
|
|
28337
|
-
const { getAdapter:
|
|
28586
|
+
const { getAdapter: getAdapter4, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
28338
28587
|
await probeBackendAvailability2();
|
|
28339
|
-
const claude =
|
|
28588
|
+
const claude = getAdapter4("claude");
|
|
28340
28589
|
if ("getAuthMode" in claude) claude.getAuthMode();
|
|
28341
|
-
const cursor =
|
|
28590
|
+
const cursor = getAdapter4("cursor");
|
|
28342
28591
|
if ("probeTier" in cursor) cursor.probeTier();
|
|
28343
28592
|
} catch {
|
|
28344
28593
|
}
|
|
@@ -28358,8 +28607,8 @@ async function main() {
|
|
|
28358
28607
|
}
|
|
28359
28608
|
if (modelCount > 0) {
|
|
28360
28609
|
try {
|
|
28361
|
-
const { getAdapter:
|
|
28362
|
-
const adapter =
|
|
28610
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
28611
|
+
const adapter = getAdapter4("ollama");
|
|
28363
28612
|
if ("refreshModelCatalog" in adapter) {
|
|
28364
28613
|
adapter.refreshModelCatalog();
|
|
28365
28614
|
}
|
|
@@ -29144,7 +29393,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
29144
29393
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
29145
29394
|
const readDb = openDatabaseReadOnly2();
|
|
29146
29395
|
const chatId = resolveChatId2(globalOpts);
|
|
29147
|
-
const { getAdapterForChat:
|
|
29396
|
+
const { getAdapterForChat: getAdapterForChat2, getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
29148
29397
|
let backend2 = null;
|
|
29149
29398
|
let modelName = "not set";
|
|
29150
29399
|
let contextMax = 2e5;
|
|
@@ -29152,7 +29401,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
29152
29401
|
const backendRow = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
|
|
29153
29402
|
if (backendRow) {
|
|
29154
29403
|
try {
|
|
29155
|
-
const a =
|
|
29404
|
+
const a = getAdapter4(backendRow.backend);
|
|
29156
29405
|
backend2 = { id: a.id, displayName: a.displayName };
|
|
29157
29406
|
} catch {
|
|
29158
29407
|
backend2 = { id: backendRow.backend, displayName: backendRow.backend };
|
|
@@ -29165,7 +29414,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
29165
29414
|
if (modelRow) modelName = modelRow.model;
|
|
29166
29415
|
else if (backend2) {
|
|
29167
29416
|
try {
|
|
29168
|
-
modelName =
|
|
29417
|
+
modelName = getAdapter4(backend2.id).defaultModel;
|
|
29169
29418
|
} catch {
|
|
29170
29419
|
}
|
|
29171
29420
|
}
|
|
@@ -29632,9 +29881,9 @@ async function sessionLogsList(opts) {
|
|
|
29632
29881
|
`));
|
|
29633
29882
|
console.log(` ${"Filename".padEnd(55)} ${"Size".padStart(8)} Chat ID`);
|
|
29634
29883
|
console.log(` ${"\u2500".repeat(55)} ${"\u2500".repeat(8)} ${"\u2500".repeat(15)}`);
|
|
29635
|
-
for (const
|
|
29636
|
-
const size =
|
|
29637
|
-
console.log(` ${
|
|
29884
|
+
for (const log5 of logs) {
|
|
29885
|
+
const size = log5.sizeBytes < 1024 ? `${log5.sizeBytes}B` : log5.sizeBytes < 1024 * 1024 ? `${(log5.sizeBytes / 1024).toFixed(1)}K` : `${(log5.sizeBytes / 1024 / 1024).toFixed(1)}M`;
|
|
29886
|
+
console.log(` ${log5.filename.padEnd(55)} ${size.padStart(8)} ${log5.chatId}`);
|
|
29638
29887
|
}
|
|
29639
29888
|
console.log(muted(`
|
|
29640
29889
|
Path: ${SESSION_LOGS_PATH}`));
|
|
@@ -30842,7 +31091,7 @@ import { existsSync as existsSync38 } from "fs";
|
|
|
30842
31091
|
async function modelList(globalOpts) {
|
|
30843
31092
|
const chatId = resolveChatId2(globalOpts);
|
|
30844
31093
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
30845
|
-
const { getAdapter:
|
|
31094
|
+
const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
30846
31095
|
let backendId = "claude";
|
|
30847
31096
|
if (existsSync38(DB_PATH)) {
|
|
30848
31097
|
const readDb = openDatabaseReadOnly2();
|
|
@@ -30854,7 +31103,7 @@ async function modelList(globalOpts) {
|
|
|
30854
31103
|
readDb.close();
|
|
30855
31104
|
}
|
|
30856
31105
|
try {
|
|
30857
|
-
const adapter =
|
|
31106
|
+
const adapter = getAdapter4(backendId);
|
|
30858
31107
|
const models = Object.entries(adapter.availableModels).map(([id, info]) => ({
|
|
30859
31108
|
id,
|
|
30860
31109
|
label: info.label,
|
|
@@ -32178,8 +32427,8 @@ var init_voice = __esm({
|
|
|
32178
32427
|
});
|
|
32179
32428
|
|
|
32180
32429
|
// src/cli/commands/heartbeat.ts
|
|
32181
|
-
var
|
|
32182
|
-
__export(
|
|
32430
|
+
var heartbeat_exports2 = {};
|
|
32431
|
+
__export(heartbeat_exports2, {
|
|
32183
32432
|
heartbeatGet: () => heartbeatGet,
|
|
32184
32433
|
heartbeatSet: () => heartbeatSet
|
|
32185
32434
|
});
|
|
@@ -34626,11 +34875,11 @@ voice.command("set <on|off>").description("Set voice on or off").action(async (v
|
|
|
34626
34875
|
});
|
|
34627
34876
|
var heartbeat = program.command("heartbeat").description("Proactive awareness");
|
|
34628
34877
|
heartbeat.command("get").description("Show heartbeat status and config").action(async () => {
|
|
34629
|
-
const { heartbeatGet: heartbeatGet2 } = await Promise.resolve().then(() => (init_heartbeat3(),
|
|
34878
|
+
const { heartbeatGet: heartbeatGet2 } = await Promise.resolve().then(() => (init_heartbeat3(), heartbeat_exports2));
|
|
34630
34879
|
await heartbeatGet2(program.opts());
|
|
34631
34880
|
});
|
|
34632
34881
|
heartbeat.command("set <subcommand> [value]").description("Configure heartbeat (on/off/interval/hours/backend/model/thinking/fallbacks)").action(async (subcommand, value) => {
|
|
34633
|
-
const { heartbeatSet: heartbeatSet2 } = await Promise.resolve().then(() => (init_heartbeat3(),
|
|
34882
|
+
const { heartbeatSet: heartbeatSet2 } = await Promise.resolve().then(() => (init_heartbeat3(), heartbeat_exports2));
|
|
34634
34883
|
await heartbeatSet2(program.opts(), subcommand, value);
|
|
34635
34884
|
});
|
|
34636
34885
|
var summarizer = program.command("summarizer").description("Session summarizer config");
|