cc-claw 0.20.9 → 0.20.11
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 +935 -647
- 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.11" : (() => {
|
|
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);
|
|
@@ -14561,9 +14586,637 @@ var init_propose = __esm({
|
|
|
14561
14586
|
}
|
|
14562
14587
|
});
|
|
14563
14588
|
|
|
14564
|
-
// src/
|
|
14565
|
-
|
|
14589
|
+
// src/channels/telegram-throttle.ts
|
|
14590
|
+
var telegram_throttle_exports = {};
|
|
14591
|
+
__export(telegram_throttle_exports, {
|
|
14592
|
+
TelegramThrottle: () => TelegramThrottle,
|
|
14593
|
+
getThrottleState: () => getThrottleState
|
|
14594
|
+
});
|
|
14595
|
+
import { GrammyError } from "grammy";
|
|
14596
|
+
function getThrottleState() {
|
|
14597
|
+
if (!_activeThrottle) return null;
|
|
14598
|
+
return _activeThrottle.getState();
|
|
14599
|
+
}
|
|
14600
|
+
function is429(err) {
|
|
14601
|
+
return err instanceof GrammyError && err.error_code === 429;
|
|
14602
|
+
}
|
|
14603
|
+
function sleep(ms) {
|
|
14604
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
14605
|
+
}
|
|
14606
|
+
var PER_CHAT_INTERVAL_MS, GLOBAL_INTERVAL_MS, MAX_RETRIES2, RETRY_DELAY_MS, MAX_QUEUE_SIZE, MAX_TOTAL_PAUSE_MS, _activeThrottle, TelegramThrottle;
|
|
14607
|
+
var init_telegram_throttle = __esm({
|
|
14608
|
+
"src/channels/telegram-throttle.ts"() {
|
|
14609
|
+
"use strict";
|
|
14610
|
+
init_log();
|
|
14611
|
+
PER_CHAT_INTERVAL_MS = 350;
|
|
14612
|
+
GLOBAL_INTERVAL_MS = 50;
|
|
14613
|
+
MAX_RETRIES2 = 2;
|
|
14614
|
+
RETRY_DELAY_MS = 1e3;
|
|
14615
|
+
MAX_QUEUE_SIZE = 100;
|
|
14616
|
+
MAX_TOTAL_PAUSE_MS = 30 * 60 * 1e3;
|
|
14617
|
+
_activeThrottle = null;
|
|
14618
|
+
TelegramThrottle = class {
|
|
14619
|
+
queue = [];
|
|
14620
|
+
processing = false;
|
|
14621
|
+
lastSendPerChat = /* @__PURE__ */ new Map();
|
|
14622
|
+
lastGlobalSend = 0;
|
|
14623
|
+
// Pause state
|
|
14624
|
+
pausedUntil = 0;
|
|
14625
|
+
pauseStartedAt = 0;
|
|
14626
|
+
chatsPendingNotification = /* @__PURE__ */ new Set();
|
|
14627
|
+
resumeNotifier;
|
|
14628
|
+
constructor() {
|
|
14629
|
+
_activeThrottle = this;
|
|
14630
|
+
}
|
|
14631
|
+
/**
|
|
14632
|
+
* Register a callback that fires when the throttle resumes after a pause.
|
|
14633
|
+
* The callback should send a message directly via bot.api (NOT through the throttle).
|
|
14634
|
+
*/
|
|
14635
|
+
setResumeNotifier(fn) {
|
|
14636
|
+
this.resumeNotifier = fn;
|
|
14637
|
+
}
|
|
14638
|
+
/** Enqueue a Telegram API call with automatic pacing and 429 handling. */
|
|
14639
|
+
async send(chatId, label2, fn) {
|
|
14640
|
+
return new Promise((resolve, reject) => {
|
|
14641
|
+
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
14642
|
+
const dropped = this.queue.shift();
|
|
14643
|
+
if (dropped) {
|
|
14644
|
+
warn(`[throttle] Queue full (${MAX_QUEUE_SIZE}), dropping oldest: ${dropped.label}`);
|
|
14645
|
+
dropped.reject(new Error("Dropped from send queue (overflow)"));
|
|
14646
|
+
}
|
|
14647
|
+
}
|
|
14648
|
+
this.queue.push({ chatId, label: label2, fn, resolve, reject });
|
|
14649
|
+
this.drain();
|
|
14650
|
+
});
|
|
14651
|
+
}
|
|
14652
|
+
/** Check whether the throttle is currently paused (rate-limited). */
|
|
14653
|
+
isPaused() {
|
|
14654
|
+
return Date.now() < this.pausedUntil;
|
|
14655
|
+
}
|
|
14656
|
+
/** Get structured state for diagnostics / health checks. */
|
|
14657
|
+
getState() {
|
|
14658
|
+
const now = Date.now();
|
|
14659
|
+
const paused = now < this.pausedUntil;
|
|
14660
|
+
return {
|
|
14661
|
+
isPaused: paused,
|
|
14662
|
+
queueDepth: this.queue.length,
|
|
14663
|
+
pausedUntilMs: this.pausedUntil,
|
|
14664
|
+
pauseRemainingSec: paused ? Math.ceil((this.pausedUntil - now) / 1e3) : 0
|
|
14665
|
+
};
|
|
14666
|
+
}
|
|
14667
|
+
// ── Queue processor ─────────────────────────────────────────────────
|
|
14668
|
+
async drain() {
|
|
14669
|
+
if (this.processing) return;
|
|
14670
|
+
this.processing = true;
|
|
14671
|
+
try {
|
|
14672
|
+
while (this.queue.length > 0) {
|
|
14673
|
+
while (this.isPaused()) {
|
|
14674
|
+
if (this.pauseStartedAt > 0 && Date.now() - this.pauseStartedAt > MAX_TOTAL_PAUSE_MS) {
|
|
14675
|
+
warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items`);
|
|
14676
|
+
this.flushQueueWithError("Telegram rate limit exceeded max wait time");
|
|
14677
|
+
this.pausedUntil = 0;
|
|
14678
|
+
this.pauseStartedAt = 0;
|
|
14679
|
+
this.chatsPendingNotification.clear();
|
|
14680
|
+
break;
|
|
14681
|
+
}
|
|
14682
|
+
const waitMs = Math.min(this.pausedUntil - Date.now(), 5e3);
|
|
14683
|
+
if (waitMs > 0) await sleep(waitMs);
|
|
14684
|
+
}
|
|
14685
|
+
if (this.queue.length === 0) break;
|
|
14686
|
+
if (this.chatsPendingNotification.size > 0) {
|
|
14687
|
+
await this.sendResumeNotifications();
|
|
14688
|
+
}
|
|
14689
|
+
const item = this.queue[0];
|
|
14690
|
+
const lastChat = this.lastSendPerChat.get(item.chatId) ?? 0;
|
|
14691
|
+
const chatWait = PER_CHAT_INTERVAL_MS - (Date.now() - lastChat);
|
|
14692
|
+
if (chatWait > 0) await sleep(chatWait);
|
|
14693
|
+
const globalWait = GLOBAL_INTERVAL_MS - (Date.now() - this.lastGlobalSend);
|
|
14694
|
+
if (globalWait > 0) await sleep(globalWait);
|
|
14695
|
+
this.queue.shift();
|
|
14696
|
+
try {
|
|
14697
|
+
const result = await this.execWithRetry(item.label, item.fn);
|
|
14698
|
+
this.recordSend(item.chatId);
|
|
14699
|
+
item.resolve(result);
|
|
14700
|
+
} catch (err) {
|
|
14701
|
+
if (is429(err)) {
|
|
14702
|
+
const retrySec = err.parameters?.retry_after ?? 10;
|
|
14703
|
+
this.enterPause(retrySec, item);
|
|
14704
|
+
continue;
|
|
14705
|
+
}
|
|
14706
|
+
item.reject(err);
|
|
14707
|
+
}
|
|
14708
|
+
}
|
|
14709
|
+
} finally {
|
|
14710
|
+
this.processing = false;
|
|
14711
|
+
}
|
|
14712
|
+
}
|
|
14713
|
+
// ── Retry logic (non-429 errors only) ───────────────────────────────
|
|
14714
|
+
async execWithRetry(label2, fn) {
|
|
14715
|
+
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
14716
|
+
try {
|
|
14717
|
+
return await fn();
|
|
14718
|
+
} catch (err) {
|
|
14719
|
+
if (is429(err)) throw err;
|
|
14720
|
+
if (attempt < MAX_RETRIES2 && err instanceof GrammyError) {
|
|
14721
|
+
warn(`[throttle] ${label2} attempt ${attempt + 1}/${MAX_RETRIES2} failed (${err.error_code}), retrying`);
|
|
14722
|
+
await sleep(RETRY_DELAY_MS);
|
|
14723
|
+
continue;
|
|
14724
|
+
}
|
|
14725
|
+
throw err;
|
|
14726
|
+
}
|
|
14727
|
+
}
|
|
14728
|
+
throw new Error("unreachable");
|
|
14729
|
+
}
|
|
14730
|
+
// ── Pause management ────────────────────────────────────────────────
|
|
14731
|
+
enterPause(retrySec, failedItem) {
|
|
14732
|
+
this.queue.unshift(failedItem);
|
|
14733
|
+
this.pausedUntil = Date.now() + retrySec * 1e3;
|
|
14734
|
+
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
14735
|
+
for (const qi of this.queue) {
|
|
14736
|
+
this.chatsPendingNotification.add(qi.chatId);
|
|
14737
|
+
}
|
|
14738
|
+
warn(`[throttle] 429 \u2014 pausing ALL sends for ${retrySec}s (${this.queue.length} items queued)`);
|
|
14739
|
+
}
|
|
14740
|
+
async sendResumeNotifications() {
|
|
14741
|
+
const chats2 = new Set(this.chatsPendingNotification);
|
|
14742
|
+
this.chatsPendingNotification.clear();
|
|
14743
|
+
if (!this.resumeNotifier) return;
|
|
14744
|
+
const pausedSec = this.pauseStartedAt > 0 ? Math.round((Date.now() - this.pauseStartedAt) / 1e3) : 0;
|
|
14745
|
+
this.pauseStartedAt = 0;
|
|
14746
|
+
for (const chatId of chats2) {
|
|
14747
|
+
const queuedForChat = this.queue.filter((q) => q.chatId === chatId).length;
|
|
14748
|
+
if (queuedForChat === 0) continue;
|
|
14749
|
+
try {
|
|
14750
|
+
await this.resumeNotifier(chatId, pausedSec, queuedForChat);
|
|
14751
|
+
this.recordSend(chatId);
|
|
14752
|
+
} catch (err) {
|
|
14753
|
+
if (is429(err)) {
|
|
14754
|
+
const retrySec = err.parameters?.retry_after ?? 10;
|
|
14755
|
+
this.pausedUntil = Date.now() + retrySec * 1e3;
|
|
14756
|
+
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
14757
|
+
for (const c of chats2) this.chatsPendingNotification.add(c);
|
|
14758
|
+
warn(`[throttle] Resume notification hit 429, re-pausing for ${retrySec}s`);
|
|
14759
|
+
return;
|
|
14760
|
+
}
|
|
14761
|
+
warn(`[throttle] Resume notification failed for chat ${chatId}: ${err}`);
|
|
14762
|
+
}
|
|
14763
|
+
}
|
|
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, {
|
|
@@ -19134,7 +19349,10 @@ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
|
|
|
19134
19349
|
}
|
|
19135
19350
|
if (fallbacks.length > 0) {
|
|
19136
19351
|
buttons.push([
|
|
19137
|
-
{ label: fallbacks.map((f) =>
|
|
19352
|
+
{ label: fallbacks.map((f) => {
|
|
19353
|
+
const name = capitalize(f.backend);
|
|
19354
|
+
return f.model ? `${name} (${shortModelName(f.model)})` : name;
|
|
19355
|
+
}).join(" \u2192 "), data: "hb:noop" },
|
|
19138
19356
|
{ label: "Clear All", data: "hb:fb:clear", style: "danger" }
|
|
19139
19357
|
]);
|
|
19140
19358
|
}
|
|
@@ -22299,12 +22517,44 @@ async function handleSummarizerCommand(chatId, commandArgs, msg, channel) {
|
|
|
22299
22517
|
[{ label: `Off (disable)${isOff ? " \u2713" : ""}`, data: "summarizer:off", ...isOff ? { style: "primary" } : {} }]
|
|
22300
22518
|
];
|
|
22301
22519
|
for (const a of getAvailableAdapters()) {
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
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
|
+
}
|
|
22308
22558
|
}
|
|
22309
22559
|
await channel.sendKeyboard(chatId, `Session summarizer (current: ${currentLabel}):`, buttons);
|
|
22310
22560
|
} else {
|
|
@@ -23272,7 +23522,21 @@ function parseExtractedSkill(llmResponse) {
|
|
|
23272
23522
|
let content = llmResponse;
|
|
23273
23523
|
const fenceMatch = llmResponse.match(/```(?:markdown|md)?\s*\n([\s\S]*?)```/);
|
|
23274
23524
|
if (fenceMatch) content = fenceMatch[1].trim();
|
|
23275
|
-
|
|
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
|
+
}
|
|
23276
23540
|
if (!fmMatch) {
|
|
23277
23541
|
warn("[auto-skill] No frontmatter found in extracted skill");
|
|
23278
23542
|
return null;
|
|
@@ -24024,8 +24288,8 @@ ${rotationNote}`, { parseMode: "html" });
|
|
|
24024
24288
|
if (action === "toggle") {
|
|
24025
24289
|
const backend2 = parts[2];
|
|
24026
24290
|
const model2 = parts[3];
|
|
24027
|
-
const { getAdapter:
|
|
24028
|
-
const toggleAdapter =
|
|
24291
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
24292
|
+
const toggleAdapter = getAdapter4(backend2);
|
|
24029
24293
|
const label2 = toggleAdapter.availableModels[model2]?.label ?? model2;
|
|
24030
24294
|
const { toggleParticipant: toggleParticipant2, buildSelectKeyboard: buildSelectKeyboard2, hasPendingCouncil: hasPendingCouncil2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports));
|
|
24031
24295
|
if (!hasPendingCouncil2(chatId)) {
|
|
@@ -24271,54 +24535,38 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
|
|
|
24271
24535
|
}
|
|
24272
24536
|
return;
|
|
24273
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));
|
|
24274
24539
|
const rest = data.slice(3);
|
|
24275
24540
|
if (rest === "on") {
|
|
24276
|
-
|
|
24277
|
-
startHeartbeatForChat(chatId);
|
|
24541
|
+
enableHeartbeat2(chatId);
|
|
24278
24542
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24279
24543
|
} else if (rest === "off") {
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
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" });
|
|
24283
24546
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24284
24547
|
} else if (rest.startsWith("interval:")) {
|
|
24285
24548
|
const min = parseInt(rest.slice(9), 10);
|
|
24286
24549
|
if (isNaN(min) || min < 1) return;
|
|
24287
|
-
|
|
24288
|
-
setHeartbeatConfig(chatId, { intervalMs: ms });
|
|
24289
|
-
stopHeartbeatForChat(chatId);
|
|
24290
|
-
const hbConf = getHeartbeatConfig(chatId);
|
|
24291
|
-
if (hbConf?.enabled) startHeartbeatForChat(chatId);
|
|
24550
|
+
updateHeartbeatConfig2({ intervalMs: min * 6e4 });
|
|
24292
24551
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24293
24552
|
} else if (rest.startsWith("backend:")) {
|
|
24294
24553
|
const bid = rest.slice(8);
|
|
24295
24554
|
if (bid === "default") {
|
|
24296
|
-
|
|
24297
|
-
updateHeartbeatField(chatId, "model", null);
|
|
24555
|
+
updateHeartbeatConfig2({ backend: null, model: null });
|
|
24298
24556
|
} else {
|
|
24299
|
-
|
|
24300
|
-
updateHeartbeatField(chatId, "model", null);
|
|
24557
|
+
updateHeartbeatConfig2({ backend: bid, model: null });
|
|
24301
24558
|
}
|
|
24302
24559
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24303
24560
|
} else if (rest.startsWith("model:")) {
|
|
24304
24561
|
const model2 = rest.slice(6);
|
|
24305
|
-
|
|
24306
|
-
updateHeartbeatField(chatId, "model", null);
|
|
24307
|
-
} else {
|
|
24308
|
-
updateHeartbeatField(chatId, "model", model2);
|
|
24309
|
-
}
|
|
24562
|
+
updateHeartbeatConfig2({ model: model2 === "default" ? null : model2 });
|
|
24310
24563
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24311
24564
|
} else if (rest === "thinking") {
|
|
24312
|
-
|
|
24313
|
-
const current = config2?.thinking ?? "off";
|
|
24314
|
-
const cycle = ["off", "low", "medium", "high"];
|
|
24315
|
-
const next = cycle[(cycle.indexOf(current) + 1) % cycle.length];
|
|
24316
|
-
updateHeartbeatField(chatId, "thinking", next === "off" ? null : next);
|
|
24317
|
-
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24565
|
+
await channel.sendText(chatId, "Thinking level for heartbeat: use /editjob to configure.", { parseMode: "plain" });
|
|
24318
24566
|
} else if (rest === "run") {
|
|
24319
24567
|
await channel.sendText(chatId, "\u23F3 Running heartbeat check...", { parseMode: "plain" });
|
|
24320
24568
|
try {
|
|
24321
|
-
await
|
|
24569
|
+
await triggerHb();
|
|
24322
24570
|
await channel.sendText(chatId, "\u2705 Heartbeat check complete.", { parseMode: "plain" });
|
|
24323
24571
|
} catch (err) {
|
|
24324
24572
|
await channel.sendText(chatId, `\u274C Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, { parseMode: "plain" });
|
|
@@ -24327,13 +24575,49 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
|
|
|
24327
24575
|
await channel.sendText(chatId, "Use /watch to manage awareness tasks.\n\nExamples:\n /watch add Check my email for urgent messages\n /watch add Check if Ollama is running\n /watch list\n /watch remove 1", { parseMode: "plain" });
|
|
24328
24576
|
} else if (rest.startsWith("fb:add:")) {
|
|
24329
24577
|
const bid = rest.slice(7);
|
|
24578
|
+
try {
|
|
24579
|
+
const adapter = getAdapter(bid);
|
|
24580
|
+
const models = Object.entries(adapter.availableModels);
|
|
24581
|
+
const rows = [
|
|
24582
|
+
[{ label: `Select model for ${capitalize(bid)} fallback:`, data: "hb:noop" }],
|
|
24583
|
+
[{ label: "Default", data: `hb:fb:pick:${bid}:default`, style: "primary" }]
|
|
24584
|
+
];
|
|
24585
|
+
if (models.length > 0) {
|
|
24586
|
+
rows.push(models.slice(0, 4).map(([key]) => ({
|
|
24587
|
+
label: shortModelName(key),
|
|
24588
|
+
data: `hb:fb:pick:${bid}:${key}`
|
|
24589
|
+
})));
|
|
24590
|
+
}
|
|
24591
|
+
rows.push([{ label: "\u2190 Back", data: "hb:fb:back" }]);
|
|
24592
|
+
await sendOrEditKeyboard(
|
|
24593
|
+
chatId,
|
|
24594
|
+
channel,
|
|
24595
|
+
messageId,
|
|
24596
|
+
`Pick a model for the ${capitalize(bid)} fallback:`,
|
|
24597
|
+
rows
|
|
24598
|
+
);
|
|
24599
|
+
} catch {
|
|
24600
|
+
const config2 = getHeartbeatConfig(chatId);
|
|
24601
|
+
const current = parseHeartbeatFallbacks(config2?.fallbacks ?? null);
|
|
24602
|
+
if (!current.some((f) => f.backend === bid)) {
|
|
24603
|
+
current.push({ backend: bid });
|
|
24604
|
+
updateHeartbeatField(chatId, "fallbacks", JSON.stringify(current));
|
|
24605
|
+
}
|
|
24606
|
+
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24607
|
+
}
|
|
24608
|
+
} else if (rest.startsWith("fb:pick:")) {
|
|
24609
|
+
const parts = rest.slice(8).split(":");
|
|
24610
|
+
const bid = parts[0];
|
|
24611
|
+
const model2 = parts.slice(1).join(":");
|
|
24330
24612
|
const config2 = getHeartbeatConfig(chatId);
|
|
24331
24613
|
const current = parseHeartbeatFallbacks(config2?.fallbacks ?? null);
|
|
24332
24614
|
if (!current.some((f) => f.backend === bid)) {
|
|
24333
|
-
current.push({ backend: bid });
|
|
24615
|
+
current.push(model2 === "default" ? { backend: bid } : { backend: bid, model: model2 });
|
|
24334
24616
|
updateHeartbeatField(chatId, "fallbacks", JSON.stringify(current));
|
|
24335
24617
|
}
|
|
24336
24618
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24619
|
+
} else if (rest === "fb:back") {
|
|
24620
|
+
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
24337
24621
|
} else if (rest === "fb:clear") {
|
|
24338
24622
|
updateHeartbeatField(chatId, "fallbacks", null);
|
|
24339
24623
|
await sendHeartbeatKeyboard(chatId, channel, messageId);
|
|
@@ -24532,7 +24816,6 @@ var init_callbacks = __esm({
|
|
|
24532
24816
|
init_discover();
|
|
24533
24817
|
init_profile();
|
|
24534
24818
|
init_profile();
|
|
24535
|
-
init_heartbeat2();
|
|
24536
24819
|
init_store5();
|
|
24537
24820
|
init_summarize();
|
|
24538
24821
|
init_inject();
|
|
@@ -25709,7 +25992,7 @@ function startSingleJob(job) {
|
|
|
25709
25992
|
}
|
|
25710
25993
|
}
|
|
25711
25994
|
function stopJobTimer(id) {
|
|
25712
|
-
const timer =
|
|
25995
|
+
const timer = activeTimers.get(id);
|
|
25713
25996
|
if (!timer) return;
|
|
25714
25997
|
if (timer instanceof Cron) {
|
|
25715
25998
|
timer.stop();
|
|
@@ -25717,7 +26000,7 @@ function stopJobTimer(id) {
|
|
|
25717
26000
|
clearTimeout(timer);
|
|
25718
26001
|
clearInterval(timer);
|
|
25719
26002
|
}
|
|
25720
|
-
|
|
26003
|
+
activeTimers.delete(id);
|
|
25721
26004
|
}
|
|
25722
26005
|
function cancelJob(id) {
|
|
25723
26006
|
stopJobTimer(id);
|
|
@@ -25764,7 +26047,7 @@ function startCronJob(job) {
|
|
|
25764
26047
|
await executeJob(fresh);
|
|
25765
26048
|
}
|
|
25766
26049
|
});
|
|
25767
|
-
|
|
26050
|
+
activeTimers.set(job.id, cronInstance);
|
|
25768
26051
|
const nextRun = cronInstance.nextRun();
|
|
25769
26052
|
if (nextRun) {
|
|
25770
26053
|
updateJobNextRun(job.id, nextRun.toISOString());
|
|
@@ -25785,9 +26068,9 @@ function startOneShot(job) {
|
|
|
25785
26068
|
await executeJob(fresh);
|
|
25786
26069
|
}
|
|
25787
26070
|
updateJobEnabled(jobId, false);
|
|
25788
|
-
|
|
26071
|
+
activeTimers.delete(jobId);
|
|
25789
26072
|
}, Math.max(0, delay));
|
|
25790
|
-
|
|
26073
|
+
activeTimers.set(job.id, timer);
|
|
25791
26074
|
updateJobNextRun(job.id, job.atTime);
|
|
25792
26075
|
}
|
|
25793
26076
|
function startInterval(job) {
|
|
@@ -25800,7 +26083,7 @@ function startInterval(job) {
|
|
|
25800
26083
|
updateJobNextRun(jobId, new Date(Date.now() + everyMs).toISOString());
|
|
25801
26084
|
}
|
|
25802
26085
|
}, everyMs);
|
|
25803
|
-
|
|
26086
|
+
activeTimers.set(job.id, interval);
|
|
25804
26087
|
updateJobNextRun(job.id, new Date(Date.now() + everyMs).toISOString());
|
|
25805
26088
|
}
|
|
25806
26089
|
async function executeJob(job) {
|
|
@@ -25811,7 +26094,7 @@ async function executeJob(job) {
|
|
|
25811
26094
|
return;
|
|
25812
26095
|
}
|
|
25813
26096
|
runningJobs.add(job.id);
|
|
25814
|
-
const timer =
|
|
26097
|
+
const timer = activeTimers.get(job.id);
|
|
25815
26098
|
if (timer instanceof Cron) {
|
|
25816
26099
|
const nextRun = timer.nextRun();
|
|
25817
26100
|
if (nextRun) updateJobNextRun(job.id, nextRun.toISOString());
|
|
@@ -25905,6 +26188,24 @@ async function runWithRetry(job, model2, runId, t0) {
|
|
|
25905
26188
|
}
|
|
25906
26189
|
return { text: formatNightlySummary2(allInsights, totalPending) };
|
|
25907
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
|
+
}
|
|
25908
26209
|
if (job.thinking && job.thinking !== "auto") {
|
|
25909
26210
|
setThinkingLevel(chatId, job.thinking);
|
|
25910
26211
|
}
|
|
@@ -25998,13 +26299,13 @@ function resolveJobModel(job) {
|
|
|
25998
26299
|
}
|
|
25999
26300
|
}
|
|
26000
26301
|
function shutdownScheduler() {
|
|
26001
|
-
for (const [id] of
|
|
26302
|
+
for (const [id] of activeTimers) {
|
|
26002
26303
|
stopJobTimer(id);
|
|
26003
26304
|
}
|
|
26004
26305
|
stopHealthMonitor2();
|
|
26005
26306
|
log("[scheduler] Shutdown complete");
|
|
26006
26307
|
}
|
|
26007
|
-
var
|
|
26308
|
+
var activeTimers, runningJobs;
|
|
26008
26309
|
var init_cron = __esm({
|
|
26009
26310
|
"src/scheduler/cron.ts"() {
|
|
26010
26311
|
"use strict";
|
|
@@ -26015,7 +26316,7 @@ var init_cron = __esm({
|
|
|
26015
26316
|
init_delivery();
|
|
26016
26317
|
init_retry();
|
|
26017
26318
|
init_health2();
|
|
26018
|
-
|
|
26319
|
+
activeTimers = /* @__PURE__ */ new Map();
|
|
26019
26320
|
runningJobs = /* @__PURE__ */ new Set();
|
|
26020
26321
|
}
|
|
26021
26322
|
});
|
|
@@ -26161,6 +26462,16 @@ var init_wrap_backend = __esm({
|
|
|
26161
26462
|
maxConcurrentSessions: 4,
|
|
26162
26463
|
specialties: ["multi-provider", "code-generation", "analysis", "planning", "debugging"],
|
|
26163
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"
|
|
26164
26475
|
}
|
|
26165
26476
|
};
|
|
26166
26477
|
}
|
|
@@ -26690,52 +27001,7 @@ ${body.replace(/<[^>]*>/g, "").trim()}</code>
|
|
|
26690
27001
|
});
|
|
26691
27002
|
|
|
26692
27003
|
// src/channels/telegram.ts
|
|
26693
|
-
import { API_CONSTANTS, Bot, GrammyError, InlineKeyboard, InputFile } from "grammy";
|
|
26694
|
-
function tripCircuitBreaker(retrySec) {
|
|
26695
|
-
const until = Date.now() + retrySec * 1e3;
|
|
26696
|
-
if (until > circuitBreakerUntil) {
|
|
26697
|
-
circuitBreakerUntil = until;
|
|
26698
|
-
warn(`[telegram] Circuit breaker tripped \u2014 blocking ALL Telegram API calls for ${retrySec}s`);
|
|
26699
|
-
}
|
|
26700
|
-
}
|
|
26701
|
-
function isCircuitBreakerActive() {
|
|
26702
|
-
return Date.now() < circuitBreakerUntil;
|
|
26703
|
-
}
|
|
26704
|
-
function isRateLimitError(err) {
|
|
26705
|
-
return err instanceof GrammyError && err.error_code === 429;
|
|
26706
|
-
}
|
|
26707
|
-
async function withRetry(label2, fn) {
|
|
26708
|
-
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
26709
|
-
if (isCircuitBreakerActive()) {
|
|
26710
|
-
throw new GrammyError(
|
|
26711
|
-
`Circuit breaker active \u2014 skipping ${label2}`,
|
|
26712
|
-
{ ok: false, error_code: 429, description: "Rate limited (circuit breaker)" },
|
|
26713
|
-
label2,
|
|
26714
|
-
{}
|
|
26715
|
-
);
|
|
26716
|
-
}
|
|
26717
|
-
try {
|
|
26718
|
-
return await fn();
|
|
26719
|
-
} catch (err) {
|
|
26720
|
-
if (err instanceof GrammyError && err.error_code === 429) {
|
|
26721
|
-
const retrySec = err.parameters?.retry_after ?? FALLBACK_RETRY_SEC;
|
|
26722
|
-
tripCircuitBreaker(retrySec);
|
|
26723
|
-
if (retrySec > GIVE_UP_THRESHOLD_SEC) {
|
|
26724
|
-
warn(`[telegram] 429 on ${label2} \u2014 retry_after ${retrySec}s exceeds threshold, giving up`);
|
|
26725
|
-
throw err;
|
|
26726
|
-
}
|
|
26727
|
-
if (attempt < MAX_RETRIES2) {
|
|
26728
|
-
warn(`[telegram] 429 on ${label2} (attempt ${attempt + 1}/${MAX_RETRIES2}) \u2014 retrying in ${retrySec}s`);
|
|
26729
|
-
await new Promise((r) => setTimeout(r, retrySec * 1e3));
|
|
26730
|
-
continue;
|
|
26731
|
-
}
|
|
26732
|
-
warn(`[telegram] 429 on ${label2} \u2014 exhausted ${MAX_RETRIES2} retries, giving up`);
|
|
26733
|
-
}
|
|
26734
|
-
throw err;
|
|
26735
|
-
}
|
|
26736
|
-
}
|
|
26737
|
-
throw new Error(`withRetry: unreachable`);
|
|
26738
|
-
}
|
|
27004
|
+
import { API_CONSTANTS, Bot, GrammyError as GrammyError2, InlineKeyboard, InputFile } from "grammy";
|
|
26739
27005
|
function isFastPathMessage(msg) {
|
|
26740
27006
|
if (msg.type === "command" && msg.command && FAST_PATH_COMMANDS.has(msg.command)) {
|
|
26741
27007
|
return true;
|
|
@@ -26758,17 +27024,14 @@ function numericChatId(chatId) {
|
|
|
26758
27024
|
const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
|
|
26759
27025
|
return parseInt(raw);
|
|
26760
27026
|
}
|
|
26761
|
-
var
|
|
27027
|
+
var FAST_PATH_COMMANDS, TelegramChannel;
|
|
26762
27028
|
var init_telegram2 = __esm({
|
|
26763
27029
|
"src/channels/telegram.ts"() {
|
|
26764
27030
|
"use strict";
|
|
26765
27031
|
init_telegram();
|
|
26766
27032
|
init_log();
|
|
26767
27033
|
init_store5();
|
|
26768
|
-
|
|
26769
|
-
FALLBACK_RETRY_SEC = 3;
|
|
26770
|
-
GIVE_UP_THRESHOLD_SEC = 30;
|
|
26771
|
-
circuitBreakerUntil = 0;
|
|
27034
|
+
init_telegram_throttle();
|
|
26772
27035
|
FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
|
|
26773
27036
|
TelegramChannel = class {
|
|
26774
27037
|
name = "telegram";
|
|
@@ -26779,6 +27042,7 @@ var init_telegram2 = __esm({
|
|
|
26779
27042
|
agentMessageIds = /* @__PURE__ */ new Map();
|
|
26780
27043
|
// messageId → chatId
|
|
26781
27044
|
reactionHandlers = [];
|
|
27045
|
+
throttle;
|
|
26782
27046
|
constructor() {
|
|
26783
27047
|
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
26784
27048
|
if (!token) {
|
|
@@ -26794,6 +27058,17 @@ var init_telegram2 = __esm({
|
|
|
26794
27058
|
this.primaryChatId = ids[0];
|
|
26795
27059
|
this.allowedChatIds = new Set(ids);
|
|
26796
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
|
+
});
|
|
26797
27072
|
}
|
|
26798
27073
|
/** The first ID in ALLOWED_CHAT_ID — used for heartbeat, notifications, etc. */
|
|
26799
27074
|
getPrimaryChatId() {
|
|
@@ -26929,11 +27204,17 @@ var init_telegram2 = __esm({
|
|
|
26929
27204
|
}
|
|
26930
27205
|
const data = ctx.callbackQuery.data;
|
|
26931
27206
|
const messageId = ctx.callbackQuery.message?.message_id?.toString();
|
|
27207
|
+
const threadId = ctx.callbackQuery.message?.message_thread_id;
|
|
26932
27208
|
ctx.answerCallbackQuery().catch(() => {
|
|
26933
27209
|
});
|
|
26934
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
|
+
}
|
|
26935
27216
|
for (const handler2 of this.callbackHandlers) {
|
|
26936
|
-
await handler2(chatId, data,
|
|
27217
|
+
await handler2(chatId, data, ch, messageId);
|
|
26937
27218
|
}
|
|
26938
27219
|
})().catch((err) => {
|
|
26939
27220
|
error("[telegram] Callback handler error:", err);
|
|
@@ -26963,7 +27244,7 @@ var init_telegram2 = __esm({
|
|
|
26963
27244
|
});
|
|
26964
27245
|
this.bot.catch((botError) => {
|
|
26965
27246
|
const err = botError.error;
|
|
26966
|
-
if (err instanceof
|
|
27247
|
+
if (err instanceof GrammyError2) {
|
|
26967
27248
|
error(`[telegram] Grammy error ${err.error_code}: ${err.description}`);
|
|
26968
27249
|
} else {
|
|
26969
27250
|
error("[telegram] Unhandled error:", err);
|
|
@@ -26983,6 +27264,7 @@ var init_telegram2 = __esm({
|
|
|
26983
27264
|
await this.bot.stop();
|
|
26984
27265
|
}
|
|
26985
27266
|
async sendTyping(chatId, threadId) {
|
|
27267
|
+
if (this.throttle.isPaused()) return;
|
|
26986
27268
|
try {
|
|
26987
27269
|
await this.bot.api.sendChatAction(numericChatId(chatId), "typing", {
|
|
26988
27270
|
...threadId ? { message_thread_id: threadId } : {}
|
|
@@ -26997,7 +27279,8 @@ var init_telegram2 = __esm({
|
|
|
26997
27279
|
if (parseMode === "plain") {
|
|
26998
27280
|
const plainChunks = splitMessage(sanitizeForTelegram(text));
|
|
26999
27281
|
for (const chunk of plainChunks) {
|
|
27000
|
-
const sent = await
|
|
27282
|
+
const sent = await this.throttle.send(
|
|
27283
|
+
chatId,
|
|
27001
27284
|
"sendText:plain",
|
|
27002
27285
|
() => this.bot.api.sendMessage(numericChatId(chatId), chunk, { ...threadOpts, ...replyOpts })
|
|
27003
27286
|
);
|
|
@@ -27009,7 +27292,8 @@ var init_telegram2 = __esm({
|
|
|
27009
27292
|
const chunks = splitMessage(formatted);
|
|
27010
27293
|
for (const chunk of chunks) {
|
|
27011
27294
|
try {
|
|
27012
|
-
const sent = await
|
|
27295
|
+
const sent = await this.throttle.send(
|
|
27296
|
+
chatId,
|
|
27013
27297
|
"sendText:html",
|
|
27014
27298
|
() => this.bot.api.sendMessage(numericChatId(chatId), chunk, {
|
|
27015
27299
|
parse_mode: "HTML",
|
|
@@ -27019,9 +27303,9 @@ var init_telegram2 = __esm({
|
|
|
27019
27303
|
);
|
|
27020
27304
|
this.trackAgentMessage(sent.message_id, chatId);
|
|
27021
27305
|
} catch (err) {
|
|
27022
|
-
if (isRateLimitError(err)) throw err;
|
|
27023
27306
|
warn("[telegram] sendText HTML failed, falling back to plain:", err instanceof Error ? err.message : err);
|
|
27024
|
-
const sent = await
|
|
27307
|
+
const sent = await this.throttle.send(
|
|
27308
|
+
chatId,
|
|
27025
27309
|
"sendText:fallback",
|
|
27026
27310
|
() => this.bot.api.sendMessage(
|
|
27027
27311
|
numericChatId(chatId),
|
|
@@ -27034,7 +27318,8 @@ var init_telegram2 = __esm({
|
|
|
27034
27318
|
}
|
|
27035
27319
|
}
|
|
27036
27320
|
async sendVoice(chatId, audioBuffer, fileName, threadId) {
|
|
27037
|
-
await
|
|
27321
|
+
await this.throttle.send(
|
|
27322
|
+
chatId,
|
|
27038
27323
|
"sendVoice",
|
|
27039
27324
|
() => this.bot.api.sendVoice(
|
|
27040
27325
|
numericChatId(chatId),
|
|
@@ -27044,7 +27329,8 @@ var init_telegram2 = __esm({
|
|
|
27044
27329
|
);
|
|
27045
27330
|
}
|
|
27046
27331
|
async sendFile(chatId, buffer, fileName, _mimeType, threadId) {
|
|
27047
|
-
await
|
|
27332
|
+
await this.throttle.send(
|
|
27333
|
+
chatId,
|
|
27048
27334
|
"sendFile",
|
|
27049
27335
|
() => this.bot.api.sendDocument(
|
|
27050
27336
|
numericChatId(chatId),
|
|
@@ -27064,7 +27350,8 @@ var init_telegram2 = __esm({
|
|
|
27064
27350
|
const formatted = sanitizeForTelegram(parseMode === "html" ? text : parseMode === "plain" ? text : formatForTelegram(text));
|
|
27065
27351
|
const threadOpts = threadId ? { message_thread_id: threadId } : {};
|
|
27066
27352
|
const opts = parseMode === "plain" ? { ...threadOpts } : { parse_mode: "HTML", ...threadOpts };
|
|
27067
|
-
const msg = await
|
|
27353
|
+
const msg = await this.throttle.send(
|
|
27354
|
+
chatId,
|
|
27068
27355
|
"sendTextReturningId",
|
|
27069
27356
|
() => this.bot.api.sendMessage(numericChatId(chatId), formatted, opts)
|
|
27070
27357
|
);
|
|
@@ -27077,7 +27364,8 @@ var init_telegram2 = __esm({
|
|
|
27077
27364
|
async editText(chatId, messageId, text, parseMode) {
|
|
27078
27365
|
const formatted = sanitizeForTelegram(parseMode === "html" ? text : formatForTelegram(text));
|
|
27079
27366
|
try {
|
|
27080
|
-
await
|
|
27367
|
+
await this.throttle.send(
|
|
27368
|
+
chatId,
|
|
27081
27369
|
"editText:html",
|
|
27082
27370
|
() => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
|
|
27083
27371
|
parse_mode: "HTML"
|
|
@@ -27085,10 +27373,10 @@ var init_telegram2 = __esm({
|
|
|
27085
27373
|
);
|
|
27086
27374
|
return true;
|
|
27087
27375
|
} catch (err) {
|
|
27088
|
-
if (isRateLimitError(err)) return false;
|
|
27089
27376
|
warn("[telegram] editText HTML failed, trying plain fallback:", err instanceof Error ? err.message : err);
|
|
27090
27377
|
try {
|
|
27091
|
-
await
|
|
27378
|
+
await this.throttle.send(
|
|
27379
|
+
chatId,
|
|
27092
27380
|
"editText:fallback",
|
|
27093
27381
|
() => this.bot.api.editMessageText(
|
|
27094
27382
|
numericChatId(chatId),
|
|
@@ -27116,7 +27404,8 @@ var init_telegram2 = __esm({
|
|
|
27116
27404
|
}
|
|
27117
27405
|
const formatted = sanitizeForTelegram(formatForTelegram(text));
|
|
27118
27406
|
try {
|
|
27119
|
-
await
|
|
27407
|
+
await this.throttle.send(
|
|
27408
|
+
chatId,
|
|
27120
27409
|
"editKeyboard:html",
|
|
27121
27410
|
() => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
|
|
27122
27411
|
parse_mode: "HTML",
|
|
@@ -27125,9 +27414,9 @@ var init_telegram2 = __esm({
|
|
|
27125
27414
|
);
|
|
27126
27415
|
return true;
|
|
27127
27416
|
} catch (err) {
|
|
27128
|
-
if (isRateLimitError(err)) return false;
|
|
27129
27417
|
try {
|
|
27130
|
-
await
|
|
27418
|
+
await this.throttle.send(
|
|
27419
|
+
chatId,
|
|
27131
27420
|
"editKeyboard:plain",
|
|
27132
27421
|
() => this.bot.api.editMessageText(
|
|
27133
27422
|
numericChatId(chatId),
|
|
@@ -27167,7 +27456,8 @@ var init_telegram2 = __esm({
|
|
|
27167
27456
|
const formatted = sanitizeForTelegram(formatForTelegram(safeText));
|
|
27168
27457
|
const threadOpts = threadId ? { message_thread_id: threadId } : {};
|
|
27169
27458
|
try {
|
|
27170
|
-
const msg = await
|
|
27459
|
+
const msg = await this.throttle.send(
|
|
27460
|
+
chatId,
|
|
27171
27461
|
"sendKeyboard",
|
|
27172
27462
|
() => this.bot.api.sendMessage(numericChatId(chatId), formatted, {
|
|
27173
27463
|
parse_mode: "HTML",
|
|
@@ -27177,14 +27467,11 @@ var init_telegram2 = __esm({
|
|
|
27177
27467
|
);
|
|
27178
27468
|
return msg.message_id.toString();
|
|
27179
27469
|
} catch (err) {
|
|
27180
|
-
if (isRateLimitError(err)) {
|
|
27181
|
-
warn(`[telegram] sendKeyboard rate-limited, skipping all fallbacks`);
|
|
27182
|
-
return void 0;
|
|
27183
|
-
}
|
|
27184
27470
|
error(`[telegram] sendKeyboard failed (chat=${chatId}, textLen=${text.length}):`, err);
|
|
27185
27471
|
try {
|
|
27186
27472
|
const escaped = safeText.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
27187
|
-
const retryMsg = await
|
|
27473
|
+
const retryMsg = await this.throttle.send(
|
|
27474
|
+
chatId,
|
|
27188
27475
|
"sendKeyboard:plain",
|
|
27189
27476
|
() => this.bot.api.sendMessage(numericChatId(chatId), escaped, {
|
|
27190
27477
|
reply_markup: keyboard,
|
|
@@ -27193,13 +27480,13 @@ var init_telegram2 = __esm({
|
|
|
27193
27480
|
);
|
|
27194
27481
|
return retryMsg.message_id.toString();
|
|
27195
27482
|
} catch (plainErr) {
|
|
27196
|
-
if (isRateLimitError(plainErr)) return void 0;
|
|
27197
27483
|
error(`[telegram] sendKeyboard plain retry also failed:`, plainErr);
|
|
27198
27484
|
}
|
|
27199
27485
|
try {
|
|
27200
27486
|
const plainText = safeText.replace(/<[^>]+>/g, "");
|
|
27201
27487
|
if (plainText.trim()) {
|
|
27202
|
-
await
|
|
27488
|
+
await this.throttle.send(
|
|
27489
|
+
chatId,
|
|
27203
27490
|
"sendKeyboard:text-rescue",
|
|
27204
27491
|
() => this.bot.api.sendMessage(numericChatId(chatId), plainText, { ...threadOpts })
|
|
27205
27492
|
);
|
|
@@ -27207,7 +27494,8 @@ var init_telegram2 = __esm({
|
|
|
27207
27494
|
} catch {
|
|
27208
27495
|
}
|
|
27209
27496
|
try {
|
|
27210
|
-
const fallbackMsg = await
|
|
27497
|
+
const fallbackMsg = await this.throttle.send(
|
|
27498
|
+
chatId,
|
|
27211
27499
|
"sendKeyboard:fallback",
|
|
27212
27500
|
() => this.bot.api.sendMessage(numericChatId(chatId), "\u2B06\uFE0F (see above for details)", {
|
|
27213
27501
|
reply_markup: keyboard,
|
|
@@ -27541,7 +27829,7 @@ var init_bootstrap = __esm({
|
|
|
27541
27829
|
});
|
|
27542
27830
|
|
|
27543
27831
|
// src/memory/migrate-embeddings.ts
|
|
27544
|
-
function
|
|
27832
|
+
function sleep2(ms) {
|
|
27545
27833
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
27546
27834
|
}
|
|
27547
27835
|
async function migrateEmbeddings() {
|
|
@@ -27568,7 +27856,7 @@ async function migrateEmbeddings() {
|
|
|
27568
27856
|
}
|
|
27569
27857
|
}
|
|
27570
27858
|
log(`[migrate-embeddings] Embedded ${memoriesEmbedded} memories so far...`);
|
|
27571
|
-
if (batch.length === BATCH_SIZE) await
|
|
27859
|
+
if (batch.length === BATCH_SIZE) await sleep2(BATCH_DELAY_MS);
|
|
27572
27860
|
}
|
|
27573
27861
|
while (true) {
|
|
27574
27862
|
const batch = getSessionSummariesWithoutEmbeddings(BATCH_SIZE);
|
|
@@ -27586,7 +27874,7 @@ async function migrateEmbeddings() {
|
|
|
27586
27874
|
}
|
|
27587
27875
|
}
|
|
27588
27876
|
log(`[migrate-embeddings] Embedded ${sessionsEmbedded} session summaries so far...`);
|
|
27589
|
-
if (batch.length === BATCH_SIZE) await
|
|
27877
|
+
if (batch.length === BATCH_SIZE) await sleep2(BATCH_DELAY_MS);
|
|
27590
27878
|
}
|
|
27591
27879
|
log(`[migrate-embeddings] Migration complete: ${memoriesEmbedded} memories, ${sessionsEmbedded} session summaries`);
|
|
27592
27880
|
return { memories: memoriesEmbedded, sessions: sessionsEmbedded };
|
|
@@ -28295,11 +28583,11 @@ async function main() {
|
|
|
28295
28583
|
} catch {
|
|
28296
28584
|
}
|
|
28297
28585
|
try {
|
|
28298
|
-
const { getAdapter:
|
|
28586
|
+
const { getAdapter: getAdapter4, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
28299
28587
|
await probeBackendAvailability2();
|
|
28300
|
-
const claude =
|
|
28588
|
+
const claude = getAdapter4("claude");
|
|
28301
28589
|
if ("getAuthMode" in claude) claude.getAuthMode();
|
|
28302
|
-
const cursor =
|
|
28590
|
+
const cursor = getAdapter4("cursor");
|
|
28303
28591
|
if ("probeTier" in cursor) cursor.probeTier();
|
|
28304
28592
|
} catch {
|
|
28305
28593
|
}
|
|
@@ -28319,8 +28607,8 @@ async function main() {
|
|
|
28319
28607
|
}
|
|
28320
28608
|
if (modelCount > 0) {
|
|
28321
28609
|
try {
|
|
28322
|
-
const { getAdapter:
|
|
28323
|
-
const adapter =
|
|
28610
|
+
const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
28611
|
+
const adapter = getAdapter4("ollama");
|
|
28324
28612
|
if ("refreshModelCatalog" in adapter) {
|
|
28325
28613
|
adapter.refreshModelCatalog();
|
|
28326
28614
|
}
|
|
@@ -29105,7 +29393,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
29105
29393
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
29106
29394
|
const readDb = openDatabaseReadOnly2();
|
|
29107
29395
|
const chatId = resolveChatId2(globalOpts);
|
|
29108
|
-
const { getAdapterForChat:
|
|
29396
|
+
const { getAdapterForChat: getAdapterForChat2, getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
29109
29397
|
let backend2 = null;
|
|
29110
29398
|
let modelName = "not set";
|
|
29111
29399
|
let contextMax = 2e5;
|
|
@@ -29113,7 +29401,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
29113
29401
|
const backendRow = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
|
|
29114
29402
|
if (backendRow) {
|
|
29115
29403
|
try {
|
|
29116
|
-
const a =
|
|
29404
|
+
const a = getAdapter4(backendRow.backend);
|
|
29117
29405
|
backend2 = { id: a.id, displayName: a.displayName };
|
|
29118
29406
|
} catch {
|
|
29119
29407
|
backend2 = { id: backendRow.backend, displayName: backendRow.backend };
|
|
@@ -29126,7 +29414,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
29126
29414
|
if (modelRow) modelName = modelRow.model;
|
|
29127
29415
|
else if (backend2) {
|
|
29128
29416
|
try {
|
|
29129
|
-
modelName =
|
|
29417
|
+
modelName = getAdapter4(backend2.id).defaultModel;
|
|
29130
29418
|
} catch {
|
|
29131
29419
|
}
|
|
29132
29420
|
}
|
|
@@ -29593,9 +29881,9 @@ async function sessionLogsList(opts) {
|
|
|
29593
29881
|
`));
|
|
29594
29882
|
console.log(` ${"Filename".padEnd(55)} ${"Size".padStart(8)} Chat ID`);
|
|
29595
29883
|
console.log(` ${"\u2500".repeat(55)} ${"\u2500".repeat(8)} ${"\u2500".repeat(15)}`);
|
|
29596
|
-
for (const
|
|
29597
|
-
const size =
|
|
29598
|
-
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}`);
|
|
29599
29887
|
}
|
|
29600
29888
|
console.log(muted(`
|
|
29601
29889
|
Path: ${SESSION_LOGS_PATH}`));
|
|
@@ -30803,7 +31091,7 @@ import { existsSync as existsSync38 } from "fs";
|
|
|
30803
31091
|
async function modelList(globalOpts) {
|
|
30804
31092
|
const chatId = resolveChatId2(globalOpts);
|
|
30805
31093
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
30806
|
-
const { getAdapter:
|
|
31094
|
+
const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
30807
31095
|
let backendId = "claude";
|
|
30808
31096
|
if (existsSync38(DB_PATH)) {
|
|
30809
31097
|
const readDb = openDatabaseReadOnly2();
|
|
@@ -30815,7 +31103,7 @@ async function modelList(globalOpts) {
|
|
|
30815
31103
|
readDb.close();
|
|
30816
31104
|
}
|
|
30817
31105
|
try {
|
|
30818
|
-
const adapter =
|
|
31106
|
+
const adapter = getAdapter4(backendId);
|
|
30819
31107
|
const models = Object.entries(adapter.availableModels).map(([id, info]) => ({
|
|
30820
31108
|
id,
|
|
30821
31109
|
label: info.label,
|
|
@@ -32139,8 +32427,8 @@ var init_voice = __esm({
|
|
|
32139
32427
|
});
|
|
32140
32428
|
|
|
32141
32429
|
// src/cli/commands/heartbeat.ts
|
|
32142
|
-
var
|
|
32143
|
-
__export(
|
|
32430
|
+
var heartbeat_exports2 = {};
|
|
32431
|
+
__export(heartbeat_exports2, {
|
|
32144
32432
|
heartbeatGet: () => heartbeatGet,
|
|
32145
32433
|
heartbeatSet: () => heartbeatSet
|
|
32146
32434
|
});
|
|
@@ -34587,11 +34875,11 @@ voice.command("set <on|off>").description("Set voice on or off").action(async (v
|
|
|
34587
34875
|
});
|
|
34588
34876
|
var heartbeat = program.command("heartbeat").description("Proactive awareness");
|
|
34589
34877
|
heartbeat.command("get").description("Show heartbeat status and config").action(async () => {
|
|
34590
|
-
const { heartbeatGet: heartbeatGet2 } = await Promise.resolve().then(() => (init_heartbeat3(),
|
|
34878
|
+
const { heartbeatGet: heartbeatGet2 } = await Promise.resolve().then(() => (init_heartbeat3(), heartbeat_exports2));
|
|
34591
34879
|
await heartbeatGet2(program.opts());
|
|
34592
34880
|
});
|
|
34593
34881
|
heartbeat.command("set <subcommand> [value]").description("Configure heartbeat (on/off/interval/hours/backend/model/thinking/fallbacks)").action(async (subcommand, value) => {
|
|
34594
|
-
const { heartbeatSet: heartbeatSet2 } = await Promise.resolve().then(() => (init_heartbeat3(),
|
|
34882
|
+
const { heartbeatSet: heartbeatSet2 } = await Promise.resolve().then(() => (init_heartbeat3(), heartbeat_exports2));
|
|
34595
34883
|
await heartbeatSet2(program.opts(), subcommand, value);
|
|
34596
34884
|
});
|
|
34597
34885
|
var summarizer = program.command("summarizer").description("Session summarizer config");
|