cc-claw 0.20.7 → 0.20.9

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.
Files changed (2) hide show
  1. package/dist/cli.js +985 -468
  2. 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.7" : (() => {
36
+ VERSION = true ? "0.20.9" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -1625,6 +1625,30 @@ function initSchema(db3) {
1625
1625
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
1626
1626
  );
1627
1627
  `);
1628
+ try {
1629
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN backend TEXT");
1630
+ } catch {
1631
+ }
1632
+ try {
1633
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN model TEXT");
1634
+ } catch {
1635
+ }
1636
+ try {
1637
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN fallbacks TEXT");
1638
+ } catch {
1639
+ }
1640
+ try {
1641
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN target TEXT");
1642
+ } catch {
1643
+ }
1644
+ try {
1645
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN channel TEXT NOT NULL DEFAULT 'telegram'");
1646
+ } catch {
1647
+ }
1648
+ try {
1649
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN thinking TEXT");
1650
+ } catch {
1651
+ }
1628
1652
  db3.exec(`
1629
1653
  CREATE TABLE IF NOT EXISTS cwd_bookmarks (
1630
1654
  chatId TEXT NOT NULL,
@@ -3440,36 +3464,89 @@ function getHeartbeatConfig(chatId) {
3440
3464
  return getDb().prepare(
3441
3465
  `SELECT chat_id as chatId, enabled, interval_ms as intervalMs,
3442
3466
  active_start as activeStart, active_end as activeEnd,
3443
- last_beat_at as lastBeatAt, next_beat_at as nextBeatAt
3467
+ last_beat_at as lastBeatAt, next_beat_at as nextBeatAt,
3468
+ backend, model, fallbacks, target, channel, thinking
3444
3469
  FROM chat_heartbeat WHERE chat_id = ?`
3445
3470
  ).get(chatId);
3446
3471
  }
3447
3472
  function setHeartbeatConfig(chatId, config2) {
3448
3473
  getDb().prepare(`
3449
- INSERT INTO chat_heartbeat (chat_id, enabled, interval_ms, active_start, active_end)
3450
- VALUES (?, ?, ?, ?, ?)
3451
- ON CONFLICT(chat_id) DO UPDATE SET
3452
- enabled = COALESCE(?, enabled),
3453
- interval_ms = COALESCE(?, interval_ms),
3454
- active_start = COALESCE(?, active_start),
3455
- active_end = COALESCE(?, active_end)
3456
- `).run(
3457
- chatId,
3458
- config2.enabled !== void 0 ? config2.enabled ? 1 : 0 : 0,
3459
- config2.intervalMs ?? 18e5,
3460
- config2.activeStart ?? "08:00",
3461
- config2.activeEnd ?? "22:00",
3462
- config2.enabled !== void 0 ? config2.enabled ? 1 : 0 : null,
3463
- config2.intervalMs ?? null,
3464
- config2.activeStart ?? null,
3465
- config2.activeEnd ?? null
3466
- );
3474
+ INSERT OR IGNORE INTO chat_heartbeat (chat_id) VALUES (?)
3475
+ `).run(chatId);
3476
+ const updates = [];
3477
+ const values = [];
3478
+ if (config2.enabled !== void 0) {
3479
+ updates.push("enabled = ?");
3480
+ values.push(config2.enabled ? 1 : 0);
3481
+ }
3482
+ if (config2.intervalMs !== void 0) {
3483
+ updates.push("interval_ms = ?");
3484
+ values.push(config2.intervalMs);
3485
+ }
3486
+ if (config2.activeStart !== void 0) {
3487
+ updates.push("active_start = ?");
3488
+ values.push(config2.activeStart);
3489
+ }
3490
+ if (config2.activeEnd !== void 0) {
3491
+ updates.push("active_end = ?");
3492
+ values.push(config2.activeEnd);
3493
+ }
3494
+ if ("backend" in config2) {
3495
+ updates.push("backend = ?");
3496
+ values.push(config2.backend ?? null);
3497
+ }
3498
+ if ("model" in config2) {
3499
+ updates.push("model = ?");
3500
+ values.push(config2.model ?? null);
3501
+ }
3502
+ if ("fallbacks" in config2) {
3503
+ updates.push("fallbacks = ?");
3504
+ values.push(config2.fallbacks ?? null);
3505
+ }
3506
+ if ("target" in config2) {
3507
+ updates.push("target = ?");
3508
+ values.push(config2.target ?? null);
3509
+ }
3510
+ if (config2.channel !== void 0) {
3511
+ updates.push("channel = ?");
3512
+ values.push(config2.channel);
3513
+ }
3514
+ if ("thinking" in config2) {
3515
+ updates.push("thinking = ?");
3516
+ values.push(config2.thinking ?? null);
3517
+ }
3518
+ if (updates.length > 0) {
3519
+ values.push(chatId);
3520
+ getDb().prepare(
3521
+ `UPDATE chat_heartbeat SET ${updates.join(", ")} WHERE chat_id = ?`
3522
+ ).run(...values);
3523
+ }
3524
+ }
3525
+ function updateHeartbeatField(chatId, field, value) {
3526
+ const allowedFields = ["backend", "model", "fallbacks", "target", "channel", "thinking", "enabled", "interval_ms", "active_start", "active_end"];
3527
+ if (!allowedFields.includes(field)) throw new Error(`Invalid heartbeat field: ${field}`);
3528
+ getDb().prepare(`
3529
+ INSERT OR IGNORE INTO chat_heartbeat (chat_id) VALUES (?)
3530
+ `).run(chatId);
3531
+ getDb().prepare(
3532
+ `UPDATE chat_heartbeat SET ${field} = ? WHERE chat_id = ?`
3533
+ ).run(value, chatId);
3467
3534
  }
3468
3535
  function updateHeartbeatTimestamps(chatId, lastBeat, nextBeat) {
3469
3536
  getDb().prepare(
3470
3537
  "UPDATE chat_heartbeat SET last_beat_at = ?, next_beat_at = ? WHERE chat_id = ?"
3471
3538
  ).run(lastBeat, nextBeat, chatId);
3472
3539
  }
3540
+ function parseHeartbeatFallbacks(fallbacksJson) {
3541
+ if (!fallbacksJson) return [];
3542
+ try {
3543
+ const parsed = JSON.parse(fallbacksJson);
3544
+ if (!Array.isArray(parsed)) return [];
3545
+ return parsed.filter((f) => f && typeof f.backend === "string");
3546
+ } catch {
3547
+ return [];
3548
+ }
3549
+ }
3473
3550
  function getActiveWatches(chatId) {
3474
3551
  return getDb().prepare(
3475
3552
  `SELECT id, chat_id as chatId, description, expires_at as expiresAt, created_at as createdAt
@@ -3940,6 +4017,7 @@ __export(store_exports5, {
3940
4017
  markSlotExhausted: () => markSlotExhausted,
3941
4018
  markSlotSuccess: () => markSlotSuccess,
3942
4019
  openDatabaseReadOnly: () => openDatabaseReadOnly,
4020
+ parseHeartbeatFallbacks: () => parseHeartbeatFallbacks,
3943
4021
  pinChatBackendSlot: () => pinChatBackendSlot,
3944
4022
  pinChatGeminiSlot: () => pinChatGeminiSlot,
3945
4023
  pruneJobRuns: () => pruneJobRuns,
@@ -3997,6 +4075,7 @@ __export(store_exports5, {
3997
4075
  toggleShowThinkingUi: () => toggleShowThinkingUi,
3998
4076
  toggleTool: () => toggleTool,
3999
4077
  touchBookmark: () => touchBookmark,
4078
+ updateHeartbeatField: () => updateHeartbeatField,
4000
4079
  updateHeartbeatTimestamps: () => updateHeartbeatTimestamps,
4001
4080
  updateJob: () => updateJob,
4002
4081
  updateJobEnabled: () => updateJobEnabled,
@@ -6120,8 +6199,8 @@ async function healthCheck(serverName) {
6120
6199
  }
6121
6200
  if (needsCatalogRefresh) {
6122
6201
  try {
6123
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
6124
- const adapter = getAdapter4("ollama");
6202
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
6203
+ const adapter = getAdapter5("ollama");
6125
6204
  if ("refreshModelCatalog" in adapter) {
6126
6205
  adapter.refreshModelCatalog();
6127
6206
  }
@@ -6867,21 +6946,33 @@ You are an expert user and operator of the CC-Claw architecture. Because you are
6867
6946
  - **Dry Run & Webhooks**: Jobs aren't just for Telegram messages. Using \`--delivery dry_run\`, you can test jobs internally (output is logged to the database but not spammed to chat). You can also route AI output directly to external APIs using \`--delivery webhook\`.
6868
6947
  - **Auditing**: Every cron execution and its output is permanently logged in the database's \`message_log\` for verifiable auditing.
6869
6948
 
6870
- ## 5. System Controls & Modes
6949
+ ## 5. Heartbeat & Watches (Proactive Monitoring)
6950
+ - **Heartbeat**: A periodic awareness cycle that checks system health and user-defined watches. Configured via \`/heartbeat\`.
6951
+ - **Active Hours**: Heartbeat only fires within configured active hours (default 08:00-22:00).
6952
+ - **Smart Suppression**: When nothing needs attention, the AI responds with \`HEARTBEAT_OK\` and no message is delivered to the user.
6953
+ - **Delivery Parity**: Like cron jobs, heartbeat supports custom backend, model, fallback chain, thinking level, and delivery target.
6954
+ - **Watches**: AI-executable awareness tasks added via \`/watch\`. These are instructions the heartbeat AI will check using its available tools.
6955
+ - \`/watch add Check my email for urgent messages\` \u2014 adds a persistent watch
6956
+ - \`/watch add Monitor Ollama server health for 24h\` \u2014 adds a time-limited watch
6957
+ - \`/watch list\` \u2014 shows all active watches
6958
+ - \`/watch remove <id>\` \u2014 removes a watch
6959
+ - **\u26A0\uFE0F HEARTBEAT MANAGEMENT**: Heartbeat and watch configuration MUST be done exclusively through \`/heartbeat\` and \`/watch\` slash commands. NEVER modify the \`chat_heartbeat\` or \`heartbeat_watches\` database tables directly.
6960
+
6961
+ ## 6. System Controls & Modes
6871
6962
  - **Permission Modes**: Command execution is gated. \`safe\` mode requires user approval for mutations, \`yolo\` auto-runs everything without asking, and \`plan\` is strictly read-only for drafting proposals.
6872
6963
  - **Global Settings**: The user controls your behavior system-wide using commands like \`/response_style\` (concise, normal, detailed) to enforce verbosity constraints, and \`/voice_config\` to select Text-to-Speech voices from premium providers like ElevenLabs, Grok (xAI), or local macOS.
6873
6964
  - **Telegram UX**: Most slash commands use interactive inline keyboards with colored buttons. \`/menu\` (or \`/m\`) is the home screen. \`/usage\` unifies cost, limits, and usage tracking. Inline mode (\`@botname query\`) searches memories from any chat.
6874
6965
 
6875
- ## 6. Integrations & MCP
6966
+ ## 7. Integrations & MCP
6876
6967
  - **Skills**: You can load specific workflows from the \`~/.cc-claw/workspace/skills/\` directory.
6877
6968
  - **Files**: You can send physical files to the user across Telegram by simply writing \`[SEND_FILE:/absolute/path/to/file]\` in your response.
6878
6969
  - **MCP Ecosystem**: You are deeply natively integrated with Model Context Protocol (MCP) servers (like Perplexity, NotebookLM, Context7) granting you immense external reach.
6879
6970
 
6880
- ## 7. CLI Reference
6971
+ ## 8. CLI Reference
6881
6972
  - Run \\\`cc-claw --ai\\\` for a comprehensive command reference covering all CLI and Telegram commands
6882
6973
  - Use \\\`cc-claw --ai --install\\\` to install the skill to all backend skill directories
6883
6974
 
6884
- ## 8. CRITICAL RULES \u2014 Memory & State
6975
+ ## 9. CRITICAL RULES \u2014 Memory & State
6885
6976
  - NEVER use native CLI memory tools (Gemini's save_memory, Claude's memory, etc.) \u2014 they are backend-local
6886
6977
  - NEVER modify the SQLite database directly (no INSERT/UPDATE/DELETE SQL)
6887
6978
  - ALWAYS use CC-Claw slash commands (/remember, /evolve, etc.) or the cc-claw CLI
@@ -9489,8 +9580,8 @@ async function spawnSubAgent(chatId, opts) {
9489
9580
  if (!runner) throw new Error(`Unknown runner: ${opts.runner}`);
9490
9581
  if (opts.model) {
9491
9582
  try {
9492
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
9493
- const adapter = getAdapter4(opts.runner);
9583
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
9584
+ const adapter = getAdapter5(opts.runner);
9494
9585
  if (adapter.availableModels && !adapter.availableModels[opts.model]) {
9495
9586
  const validModels = Object.keys(adapter.availableModels).join(", ");
9496
9587
  throw new Error(`Unknown model "${opts.model}" for ${opts.runner}. Available: ${validModels}`);
@@ -9871,8 +9962,8 @@ async function handleAgentComplete(agentId, chatId, resultText, usage2, mcpsAdde
9871
9962
  const runner = getRunner(agent.runnerId);
9872
9963
  if (runner) {
9873
9964
  try {
9874
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
9875
- const adapter = getAdapter4(agent.runnerId);
9965
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
9966
+ const adapter = getAdapter5(agent.runnerId);
9876
9967
  const model2 = agent.model ?? adapter.defaultModel;
9877
9968
  const pricing = adapter.pricing[model2];
9878
9969
  if (pricing) {
@@ -10621,7 +10712,7 @@ var init_chat = __esm({
10621
10712
  }
10622
10713
  const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
10623
10714
  const { getMode: getMode3, getCwd: getCwd3, getModel: getModel3, addUsage: addUsage3, getBackend: getBackend3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
10624
- const { getAdapterForChat: getAdapterForChat2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
10715
+ const { getAdapterForChat: getAdapterForChat3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
10625
10716
  const chatId = body.chatId;
10626
10717
  const backend2 = body.backend;
10627
10718
  const PERM_LEVEL = { plan: 0, safe: 1, yolo: 2 };
@@ -10631,7 +10722,7 @@ var init_chat = __esm({
10631
10722
  const cwd = body.cwd ?? getCwd3(chatId);
10632
10723
  const model2 = body.model ?? getModel3(chatId) ?? (() => {
10633
10724
  try {
10634
- return getAdapterForChat2(chatId).defaultModel;
10725
+ return getAdapterForChat3(chatId).defaultModel;
10635
10726
  } catch {
10636
10727
  return void 0;
10637
10728
  }
@@ -15654,8 +15745,8 @@ async function handleAdd(chatId, channel, name, host, port) {
15654
15745
  }
15655
15746
  const models = await OllamaService.discoverModels(name);
15656
15747
  try {
15657
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
15658
- const adapter = getAdapter4("ollama");
15748
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
15749
+ const adapter = getAdapter5("ollama");
15659
15750
  if ("refreshModelCatalog" in adapter) {
15660
15751
  adapter.refreshModelCatalog();
15661
15752
  }
@@ -15708,8 +15799,8 @@ async function sendDiscover(chatId, channel, serverName) {
15708
15799
  const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
15709
15800
  const models = await OllamaService.discoverModels(serverName);
15710
15801
  try {
15711
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
15712
- const adapter = getAdapter4("ollama");
15802
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
15803
+ const adapter = getAdapter5("ollama");
15713
15804
  if ("refreshModelCatalog" in adapter) {
15714
15805
  adapter.refreshModelCatalog();
15715
15806
  }
@@ -17878,8 +17969,197 @@ var init_install = __esm({
17878
17969
  }
17879
17970
  });
17880
17971
 
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
+
17881
18161
  // src/bootstrap/heartbeat.ts
17882
- import { readFileSync as readFileSync9, existsSync as existsSync18 } from "fs";
18162
+ import { readFileSync as readFileSync10, existsSync as existsSync19 } from "fs";
17883
18163
  import { join as join19 } from "path";
17884
18164
  function initHeartbeat(channelReg) {
17885
18165
  registry2 = channelReg;
@@ -17921,6 +18201,14 @@ function stopAllHeartbeats() {
17921
18201
  function startAllHeartbeats() {
17922
18202
  try {
17923
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
+ }
17924
18212
  const rows = db3.prepare(
17925
18213
  "SELECT chat_id FROM chat_heartbeat WHERE enabled = 1"
17926
18214
  ).all();
@@ -17933,6 +18221,11 @@ function startAllHeartbeats() {
17933
18221
  } catch {
17934
18222
  }
17935
18223
  }
18224
+ async function runHeartbeatNow(chatId) {
18225
+ const config2 = getHeartbeatConfig(chatId);
18226
+ if (!config2) return;
18227
+ await runHeartbeat(chatId, config2);
18228
+ }
17936
18229
  async function runHeartbeat(chatId, config2) {
17937
18230
  if (!isWithinActiveHours(config2.activeStart, config2.activeEnd)) {
17938
18231
  log(`[heartbeat] Skipping for ${chatId}: outside active hours (${config2.activeStart}-${config2.activeEnd})`);
@@ -17940,15 +18233,17 @@ async function runHeartbeat(chatId, config2) {
17940
18233
  }
17941
18234
  cleanExpiredWatches();
17942
18235
  const prompt = assembleHeartbeatPrompt(chatId);
18236
+ const resolvedBackend = resolveBackendId(chatId, config2);
18237
+ const resolvedModel = config2.model ?? void 0;
17943
18238
  try {
17944
- const response = await askAgent(chatId, prompt, { bootstrapTier: "heartbeat" });
18239
+ const response = await askAgent(chatId, prompt, {
18240
+ bootstrapTier: "heartbeat",
18241
+ backend: resolvedBackend,
18242
+ model: resolvedModel,
18243
+ timeoutMs: 12e4
18244
+ });
17945
18245
  if (response.usage) {
17946
- let heartbeatModel;
17947
- try {
17948
- heartbeatModel = getModel(chatId) ?? getAdapterForChat(chatId).defaultModel;
17949
- } catch {
17950
- heartbeatModel = getModel(chatId) ?? "unknown";
17951
- }
18246
+ const heartbeatModel = resolvedModel ?? getModel(chatId) ?? resolvedBackend;
17952
18247
  addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, heartbeatModel, void 0, response.usage.contextSize);
17953
18248
  }
17954
18249
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -17959,14 +18254,62 @@ async function runHeartbeat(chatId, config2) {
17959
18254
  log(`[heartbeat] ${chatId}: nothing to report`);
17960
18255
  return;
17961
18256
  }
17962
- const channel = registry2?.get("telegram");
17963
- if (channel) {
17964
- await channel.sendText(chatId, response.text);
17965
- }
18257
+ await deliverHeartbeatMessage(chatId, config2, response.text);
17966
18258
  } catch (err) {
17967
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);
17968
18302
  }
17969
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
+ }
17970
18313
  function isWithinActiveHours(start, end) {
17971
18314
  try {
17972
18315
  const now = /* @__PURE__ */ new Date();
@@ -17985,55 +18328,25 @@ function isWithinActiveHours(start, end) {
17985
18328
  }
17986
18329
  function assembleHeartbeatPrompt(chatId) {
17987
18330
  const sections = [];
17988
- sections.push("You are running a periodic heartbeat check. Review the following and decide if anything needs the user's attention. If nothing is noteworthy, respond with exactly HEARTBEAT_OK and nothing else.");
17989
- const healthLines = [];
17990
- const report = getHealthReport();
17991
- if (report.failingJobs.length > 0) {
17992
- healthLines.push("Scheduler issues:");
17993
- for (const j of report.failingJobs) {
17994
- healthLines.push(` - Job #${j.id} "${j.description}": ${j.failures} consecutive failures`);
17995
- }
17996
- }
17997
- const recentRuns = getJobRuns(void 0, 20);
17998
- const failedSinceLastBeat = recentRuns.filter((r) => r.status === "failed");
17999
- if (failedSinceLastBeat.length > 0) {
18000
- healthLines.push(`${failedSinceLastBeat.length} job run(s) failed recently.`);
18001
- }
18002
- for (const backend2 of getAllBackendIds()) {
18003
- const limitMsg = checkBackendLimits(backend2);
18004
- if (limitMsg) healthLines.push(limitMsg);
18005
- }
18006
- try {
18007
- const { OllamaService } = (init_ollama(), __toCommonJS(ollama_exports));
18008
- const servers = OllamaService.listServers();
18009
- if (servers.length > 0) {
18010
- const offline = servers.filter((s) => s.status === "offline");
18011
- if (offline.length > 0) {
18012
- healthLines.push(`Ollama: ${offline.length}/${servers.length} server(s) offline: ${offline.map((s) => s.name).join(", ")}`);
18013
- }
18014
- }
18015
- } catch {
18016
- }
18017
- if (healthLines.length > 0) {
18018
- sections.push(`[System health]
18019
- ${healthLines.join("\n")}`);
18020
- } else {
18021
- sections.push("[System health]\nAll systems normal. No failures or limit warnings.");
18022
- }
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}`);
18023
18336
  const watches = getActiveWatches(chatId);
18024
18337
  if (watches.length > 0) {
18025
- const watchLines = watches.map((w) => {
18338
+ const watchLines = watches.map((w, i) => {
18026
18339
  const expiry = w.expiresAt ? ` (expires: ${w.expiresAt})` : "";
18027
- return ` - ${w.description}${expiry}`;
18340
+ return `${i + 1}. ${w.description}${expiry}`;
18028
18341
  });
18029
- sections.push(`[Active watches]
18342
+ sections.push(`[Active Watches \u2014 execute these checks using your tools]
18030
18343
  ${watchLines.join("\n")}`);
18031
18344
  }
18032
- if (existsSync18(HEARTBEAT_MD_PATH)) {
18345
+ if (existsSync19(HEARTBEAT_MD_PATH)) {
18033
18346
  try {
18034
- const custom = readFileSync9(HEARTBEAT_MD_PATH, "utf-8").trim();
18347
+ const custom = readFileSync10(HEARTBEAT_MD_PATH, "utf-8").trim();
18035
18348
  if (custom) {
18036
- sections.push(`[Custom checks]
18349
+ sections.push(`[Custom checks from HEARTBEAT.md]
18037
18350
  ${custom}`);
18038
18351
  }
18039
18352
  } catch {
@@ -18054,6 +18367,16 @@ function formatHeartbeatStatus(chatId) {
18054
18367
  `Last beat: ${config2.lastBeatAt ?? "never"}`,
18055
18368
  `Next beat: ${config2.nextBeatAt ?? "N/A"}`
18056
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
+ }
18057
18380
  if (watches.length > 0) {
18058
18381
  lines.push("", `Active watches (${watches.length}):`);
18059
18382
  for (const w of watches) {
@@ -18075,7 +18398,7 @@ var init_heartbeat2 = __esm({
18075
18398
  init_agent();
18076
18399
  init_store5();
18077
18400
  init_backends();
18078
- init_health2();
18401
+ init_checks();
18079
18402
  init_log();
18080
18403
  HEARTBEAT_MD_PATH = join19(WORKSPACE_PATH, "HEARTBEAT.md");
18081
18404
  HEARTBEAT_OK = "HEARTBEAT_OK";
@@ -18731,13 +19054,18 @@ async function sendMemoryPage(chatId, channel, page, messageId) {
18731
19054
  buttons.push(footerRow);
18732
19055
  await sendOrEditKeyboard(chatId, channel, messageId, lines.join("\n"), buttons);
18733
19056
  }
18734
- async function sendHeartbeatKeyboard(chatId, channel) {
19057
+ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
18735
19058
  const config2 = getHeartbeatConfig(chatId);
18736
19059
  const enabled = config2?.enabled === 1;
18737
19060
  const intervalMs = config2?.intervalMs ?? 18e5;
18738
19061
  const intervalMin = intervalMs / 6e4;
18739
19062
  const activeStart = config2?.activeStart ?? "08:00";
18740
19063
  const activeEnd = config2?.activeEnd ?? "22:00";
19064
+ const watches = getActiveWatches(chatId);
19065
+ const fallbacks = parseHeartbeatFallbacks(config2?.fallbacks ?? null);
19066
+ const backendDisplay = config2?.backend ? capitalize(config2.backend) : "Default";
19067
+ const modelDisplay = config2?.model ?? "default";
19068
+ const thinkingDisplay = config2?.thinking ?? "off";
18741
19069
  const lines = [
18742
19070
  buildSectionHeader("Heartbeat"),
18743
19071
  "CC-Claw periodically wakes up to check on",
@@ -18746,22 +19074,87 @@ async function sendHeartbeatKeyboard(chatId, channel) {
18746
19074
  "",
18747
19075
  `Status: ${enabled ? "ON" : "OFF"}`,
18748
19076
  `Interval: every ${intervalMin} min`,
18749
- `Active hours: ${activeStart}-${activeEnd}`
19077
+ `Active hours: ${activeStart}-${activeEnd}`,
19078
+ `Backend: ${backendDisplay} | Model: ${modelDisplay}`
18750
19079
  ];
19080
+ if (fallbacks.length > 0) {
19081
+ lines.push(`Fallbacks: ${fallbacks.map((f) => `${capitalize(f.backend)}${f.model ? `:${f.model}` : ""}`).join(", ")}`);
19082
+ }
19083
+ if (config2?.target) {
19084
+ lines.push(`Target: ${config2.target}`);
19085
+ }
19086
+ if (watches.length > 0) {
19087
+ lines.push(`Active watches: ${watches.length}`);
19088
+ }
19089
+ const lastBeat = config2?.lastBeatAt ?? "never";
19090
+ lines.push(`Last beat: ${lastBeat}`);
19091
+ const buttons = [];
19092
+ buttons.push([
19093
+ { label: `${enabled ? "\u2713 " : ""}On`, data: "hb:on", ...enabled ? { style: "success" } : {} },
19094
+ { label: `${!enabled ? "\u2713 " : ""}Off`, data: "hb:off", ...!enabled ? { style: "danger" } : {} },
19095
+ { label: "\u25B6 Run Now", data: "hb:run", style: "primary" }
19096
+ ]);
18751
19097
  const presets = [15, 30, 60, 120];
18752
- const intervalRow = presets.map((m) => ({
19098
+ buttons.push(presets.map((m) => ({
18753
19099
  label: `${m === intervalMin ? "\u2713 " : ""}${m} min`,
18754
19100
  data: `hb:interval:${m}`,
18755
19101
  ...m === intervalMin ? { style: "primary" } : {}
19102
+ })));
19103
+ const available = getAvailableBackendIds();
19104
+ buttons.push([{ label: "\u2699\uFE0F Main Engine", data: "hb:noop" }]);
19105
+ const backendRow = available.map((bid) => ({
19106
+ label: `${config2?.backend === bid ? "\u2713 " : ""}${capitalize(bid)}`,
19107
+ data: `hb:backend:${bid}`,
19108
+ ...config2?.backend === bid ? { style: "primary" } : {}
18756
19109
  }));
18757
- await channel.sendKeyboard(chatId, lines.join("\n"), [
18758
- [
18759
- { label: `${enabled ? "\u2713 " : ""}On`, data: "hb:on", ...enabled ? { style: "success" } : {} },
18760
- { label: `${!enabled ? "\u2713 " : ""}Off`, data: "hb:off", ...!enabled ? { style: "danger" } : {} }
18761
- ],
18762
- intervalRow,
18763
- [{ label: "Active Hours: /heartbeat hours <start>-<end>", data: "hb:noop" }]
18764
- ]);
19110
+ if (config2?.backend) {
19111
+ backendRow.push({ label: "\u2715", data: "hb:backend:default", style: "danger" });
19112
+ }
19113
+ buttons.push(backendRow);
19114
+ const targetBackend = config2?.backend ?? getBackend(chatId) ?? "claude";
19115
+ try {
19116
+ const adapter = getAdapter(targetBackend);
19117
+ const models = Object.entries(adapter.availableModels);
19118
+ if (models.length > 0) {
19119
+ const modelRow = models.slice(0, 4).map(([key]) => ({
19120
+ label: `${config2?.model === key ? "\u2713 " : ""}${shortModelName(key)}`,
19121
+ data: `hb:model:${key}`,
19122
+ ...config2?.model === key ? { style: "primary" } : {}
19123
+ }));
19124
+ if (config2?.model) {
19125
+ modelRow.push({ label: "\u2715", data: "hb:model:default", style: "danger" });
19126
+ }
19127
+ buttons.push(modelRow);
19128
+ }
19129
+ } catch {
19130
+ }
19131
+ const fbCandidates = available.filter((bid) => !fallbacks.some((f) => f.backend === bid) && bid !== config2?.backend);
19132
+ if (fallbacks.length > 0 || fbCandidates.length > 0) {
19133
+ buttons.push([{ label: "\u{1F504} Fallback Chain", data: "hb:noop" }]);
19134
+ }
19135
+ if (fallbacks.length > 0) {
19136
+ buttons.push([
19137
+ { label: fallbacks.map((f) => capitalize(f.backend)).join(" \u2192 "), data: "hb:noop" },
19138
+ { label: "Clear All", data: "hb:fb:clear", style: "danger" }
19139
+ ]);
19140
+ }
19141
+ if (fbCandidates.length > 0) {
19142
+ buttons.push(fbCandidates.slice(0, 4).map((bid) => ({
19143
+ label: `+ ${capitalize(bid)}`,
19144
+ data: `hb:fb:add:${bid}`
19145
+ })));
19146
+ }
19147
+ const optionsRow = [
19148
+ { label: `\u{1F4AD} Thinking: ${thinkingDisplay}`, data: "hb:thinking" }
19149
+ ];
19150
+ if (watches.length > 0) {
19151
+ optionsRow.push({ label: `\u{1F441} Watches (${watches.length})`, data: "hb:watches" });
19152
+ } else {
19153
+ optionsRow.push({ label: "+ Add Watch", data: "hb:addwatch" });
19154
+ }
19155
+ buttons.push(optionsRow);
19156
+ buttons.push([{ label: `\u23F0 ${activeStart}\u2013${activeEnd} (use /heartbeat hours)`, data: "hb:noop" }]);
19157
+ await sendOrEditKeyboard(chatId, channel, messageId, lines.join("\n"), buttons);
18765
19158
  }
18766
19159
  async function sendForgetPicker(chatId, channel, page, messageId) {
18767
19160
  const memories = listMemories();
@@ -19403,13 +19796,13 @@ async function handleEvolveCallback(chatId, data, channel) {
19403
19796
  const { getReflectionStatus: getReflectionStatus2, setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
19404
19797
  const current = getReflectionStatus2(getDb(), chatId);
19405
19798
  if (current === "frozen") {
19406
- const { readFileSync: readFileSync28, existsSync: existsSync56 } = await import("fs");
19799
+ const { readFileSync: readFileSync29, existsSync: existsSync57 } = await import("fs");
19407
19800
  const { join: join36 } = await import("path");
19408
19801
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
19409
19802
  const soulPath = join36(CC_CLAW_HOME3, "identity/SOUL.md");
19410
19803
  const userPath = join36(CC_CLAW_HOME3, "identity/USER.md");
19411
- const soul = existsSync56(soulPath) ? readFileSync28(soulPath, "utf-8") : "";
19412
- const user = existsSync56(userPath) ? readFileSync28(userPath, "utf-8") : "";
19804
+ const soul = existsSync57(soulPath) ? readFileSync29(soulPath, "utf-8") : "";
19805
+ const user = existsSync57(userPath) ? readFileSync29(userPath, "utf-8") : "";
19413
19806
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
19414
19807
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
19415
19808
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
@@ -19486,18 +19879,18 @@ var init_evolve2 = __esm({
19486
19879
  });
19487
19880
 
19488
19881
  // src/optimizer/identity-audit.ts
19489
- import { readFileSync as readFileSync10, existsSync as existsSync19, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
19882
+ import { readFileSync as readFileSync11, existsSync as existsSync20, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
19490
19883
  import { join as join21 } from "path";
19491
19884
  function readIdentityFile2(filename) {
19492
19885
  try {
19493
- return readFileSync10(join21(IDENTITY_PATH, filename), "utf-8");
19886
+ return readFileSync11(join21(IDENTITY_PATH, filename), "utf-8");
19494
19887
  } catch {
19495
19888
  return "";
19496
19889
  }
19497
19890
  }
19498
19891
  function getMtime(filepath) {
19499
19892
  try {
19500
- return statSync7(filepath).mtime.toISOString();
19893
+ return statSync8(filepath).mtime.toISOString();
19501
19894
  } catch {
19502
19895
  return "unknown";
19503
19896
  }
@@ -19506,7 +19899,7 @@ function findBackupFiles() {
19506
19899
  const backups = [];
19507
19900
  const dirs = [IDENTITY_PATH];
19508
19901
  const contextDir = join21(IDENTITY_PATH, "..", "workspace", "context");
19509
- if (existsSync19(contextDir)) dirs.push(contextDir);
19902
+ if (existsSync20(contextDir)) dirs.push(contextDir);
19510
19903
  for (const dir of dirs) {
19511
19904
  try {
19512
19905
  for (const entry of readdirSync10(dir)) {
@@ -19643,7 +20036,7 @@ var init_identity_audit = __esm({
19643
20036
  });
19644
20037
 
19645
20038
  // src/optimizer/skill-audit.ts
19646
- import { readFileSync as readFileSync11, existsSync as existsSync20 } from "fs";
20039
+ import { readFileSync as readFileSync12, existsSync as existsSync21 } from "fs";
19647
20040
  import { join as join22, basename as basename3 } from "path";
19648
20041
  function parseFrontmatter3(content) {
19649
20042
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
@@ -19684,7 +20077,7 @@ function detectDependentSkills(content) {
19684
20077
  return Array.from(deps);
19685
20078
  }
19686
20079
  function computeSkillStats(skillPath) {
19687
- const content = readFileSync11(skillPath, "utf-8");
20080
+ const content = readFileSync12(skillPath, "utf-8");
19688
20081
  const lines = content.split("\n");
19689
20082
  return {
19690
20083
  skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join22(skillPath, "..")) : basename3(skillPath, ".md"),
@@ -19711,9 +20104,9 @@ function loadDependentSkillContents(depNames, ccClawSkillsDir) {
19711
20104
  join22(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
19712
20105
  ];
19713
20106
  for (const candidate of candidates) {
19714
- if (existsSync20(candidate)) {
20107
+ if (existsSync21(candidate)) {
19715
20108
  try {
19716
- const content = readFileSync11(candidate, "utf-8");
20109
+ const content = readFileSync12(candidate, "utf-8");
19717
20110
  results.push({
19718
20111
  name,
19719
20112
  content: content.length > 3e3 ? content.slice(0, 3e3) + "\n[...truncated]" : content
@@ -19828,7 +20221,7 @@ __export(analyze_exports2, {
19828
20221
  });
19829
20222
  import { spawn as spawn7 } from "child_process";
19830
20223
  import { createInterface as createInterface7 } from "readline";
19831
- import { readFileSync as readFileSync12, existsSync as existsSync21, readdirSync as readdirSync12 } from "fs";
20224
+ import { readFileSync as readFileSync13, existsSync as existsSync22, readdirSync as readdirSync12 } from "fs";
19832
20225
  import { join as join23 } from "path";
19833
20226
  import { homedir as homedir7 } from "os";
19834
20227
  function parseOptimizeOutput(raw, validAreas) {
@@ -19959,7 +20352,7 @@ function getModelDisplayInfo(chatId) {
19959
20352
  }
19960
20353
  function readIdentityFile3(filename) {
19961
20354
  try {
19962
- return readFileSync12(join23(IDENTITY_PATH, filename), "utf-8");
20355
+ return readFileSync13(join23(IDENTITY_PATH, filename), "utf-8");
19963
20356
  } catch {
19964
20357
  return "";
19965
20358
  }
@@ -19967,12 +20360,12 @@ function readIdentityFile3(filename) {
19967
20360
  function loadContextFiles2() {
19968
20361
  const contextDir = join23(homedir7(), ".cc-claw", "workspace", "context");
19969
20362
  const results = [];
19970
- if (!existsSync21(contextDir)) return results;
20363
+ if (!existsSync22(contextDir)) return results;
19971
20364
  try {
19972
20365
  for (const entry of readdirSync12(contextDir)) {
19973
20366
  if (!entry.endsWith(".md")) continue;
19974
20367
  try {
19975
- const content = readFileSync12(join23(contextDir, entry), "utf-8");
20368
+ const content = readFileSync13(join23(contextDir, entry), "utf-8");
19976
20369
  results.push({ name: entry, content });
19977
20370
  } catch {
19978
20371
  }
@@ -20024,7 +20417,7 @@ async function runSkillAudit(chatId, skillPath) {
20024
20417
  log(`[optimizer] Running skill audit on ${stats.skillName} with ${adapter.id}:${model2}`);
20025
20418
  const soulMd = readIdentityFile3("SOUL.md");
20026
20419
  const ccClawSkillsDir = join23(homedir7(), ".cc-claw", "workspace", "skills");
20027
- const skillContent = readFileSync12(skillPath, "utf-8");
20420
+ const skillContent = readFileSync13(skillPath, "utf-8");
20028
20421
  const prompt = buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir);
20029
20422
  const raw = await spawnAnalysis2(adapter, model2, prompt);
20030
20423
  const findings = parseOptimizeOutput(raw, VALID_SKILL_AREAS);
@@ -20040,14 +20433,14 @@ async function runSkillAudit(chatId, skillPath) {
20040
20433
  function listCcClawSkills() {
20041
20434
  const skillsDir = join23(homedir7(), ".cc-claw", "workspace", "skills");
20042
20435
  const entries = [];
20043
- if (!existsSync21(skillsDir)) return entries;
20436
+ if (!existsSync22(skillsDir)) return entries;
20044
20437
  try {
20045
20438
  for (const dir of readdirSync12(skillsDir)) {
20046
20439
  const skillFile = join23(skillsDir, dir, "SKILL.md");
20047
- if (!existsSync21(skillFile)) continue;
20440
+ if (!existsSync22(skillFile)) continue;
20048
20441
  let description = "skill";
20049
20442
  try {
20050
- const content = readFileSync12(skillFile, "utf-8");
20443
+ const content = readFileSync13(skillFile, "utf-8");
20051
20444
  const descMatch = content.match(/description:\s*>?\s*\n?\s*(.+)/);
20052
20445
  if (descMatch) description = descMatch[1].trim().slice(0, 60);
20053
20446
  } catch {
@@ -20367,7 +20760,7 @@ var init_ui2 = __esm({
20367
20760
  });
20368
20761
 
20369
20762
  // src/router/optimize.ts
20370
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, existsSync as existsSync22, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
20763
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync7, existsSync as existsSync23, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
20371
20764
  import { join as join24, dirname as dirname4 } from "path";
20372
20765
  import { homedir as homedir8 } from "os";
20373
20766
  async function handleOptimizeCommand(chatId, channel, _args) {
@@ -20638,13 +21031,13 @@ async function applyFinding(chatId, channel, index) {
20638
21031
  await showFinding(chatId, channel, index + 1);
20639
21032
  return;
20640
21033
  }
20641
- if (!existsSync22(targetPath)) {
21034
+ if (!existsSync23(targetPath)) {
20642
21035
  await channel.sendText(chatId, `Target file not found: ${targetPath}`, { parseMode: "plain" });
20643
21036
  session2.skipped.push(index);
20644
21037
  await showFinding(chatId, channel, index + 1);
20645
21038
  return;
20646
21039
  }
20647
- const original = readFileSync13(targetPath, "utf-8");
21040
+ const original = readFileSync14(targetPath, "utf-8");
20648
21041
  const backupPath = targetPath + `.bak.${Date.now()}`;
20649
21042
  writeFileSync7(backupPath, original, "utf-8");
20650
21043
  pruneBackups2(targetPath);
@@ -22082,6 +22475,92 @@ async function handleHeartbeatCommand(chatId, commandArgs, msg, channel) {
22082
22475
  await channel.sendText(chatId, formatHeartbeatStatus(chatId), { parseMode: "plain" });
22083
22476
  }
22084
22477
  }
22478
+ async function handleWatchCommand(chatId, commandArgs, msg, channel) {
22479
+ if (!commandArgs) {
22480
+ const watches = getActiveWatches(chatId);
22481
+ if (watches.length === 0) {
22482
+ await channel.sendText(chatId, [
22483
+ "No active watches.",
22484
+ "",
22485
+ "Watches are awareness tasks that the heartbeat",
22486
+ "AI checks periodically using its tools.",
22487
+ "",
22488
+ "Usage:",
22489
+ " /watch add Check my email for urgent messages",
22490
+ " /watch add Monitor Ollama server health",
22491
+ " /watch add Check calendar for upcoming meetings",
22492
+ " /watch list",
22493
+ " /watch remove <id>"
22494
+ ].join("\n"), { parseMode: "plain" });
22495
+ } else {
22496
+ const lines = ["Active watches:", ""];
22497
+ for (const w of watches) {
22498
+ const expiry = w.expiresAt ? ` (until ${formatLocalDateTime(w.expiresAt)})` : "";
22499
+ lines.push(`#${w.id}: ${w.description}${expiry}`);
22500
+ }
22501
+ lines.push("", "Use /watch remove <id> to remove a watch.");
22502
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
22503
+ }
22504
+ return;
22505
+ }
22506
+ const parts = commandArgs.trim().split(/\s+/);
22507
+ const action = parts[0].toLowerCase();
22508
+ const rest = parts.slice(1).join(" ");
22509
+ switch (action) {
22510
+ case "add": {
22511
+ if (!rest) {
22512
+ await channel.sendText(chatId, "Usage: /watch add <description>\n\nExamples:\n /watch add Check my email for urgent messages\n /watch add Monitor if Ollama is running\n /watch add Check calendar for meetings in the next hour", { parseMode: "plain" });
22513
+ return;
22514
+ }
22515
+ let expiresAt;
22516
+ const durationMatch = rest.match(/\s+for\s+(\d+)([hdw])$/i);
22517
+ let description = rest;
22518
+ if (durationMatch) {
22519
+ description = rest.slice(0, durationMatch.index).trim();
22520
+ const amount = parseInt(durationMatch[1], 10);
22521
+ const unit = durationMatch[2].toLowerCase();
22522
+ const ms = unit === "h" ? amount * 36e5 : unit === "d" ? amount * 864e5 : amount * 6048e5;
22523
+ expiresAt = new Date(Date.now() + ms).toISOString();
22524
+ }
22525
+ const id = addHeartbeatWatch(chatId, description, expiresAt);
22526
+ const expiryMsg = expiresAt ? ` (expires: ${formatLocalDateTime(expiresAt)})` : "";
22527
+ await channel.sendText(chatId, `\u2705 Watch #${id} added${expiryMsg}:
22528
+ ${description}`, { parseMode: "plain" });
22529
+ return;
22530
+ }
22531
+ case "remove":
22532
+ case "delete": {
22533
+ const id = parseInt(rest, 10);
22534
+ if (isNaN(id)) {
22535
+ await channel.sendText(chatId, "Usage: /watch remove <id>", { parseMode: "plain" });
22536
+ return;
22537
+ }
22538
+ const removed = removeHeartbeatWatch(id);
22539
+ await channel.sendText(chatId, removed ? `\u2705 Watch #${id} removed.` : `Watch #${id} not found.`, { parseMode: "plain" });
22540
+ return;
22541
+ }
22542
+ case "list": {
22543
+ const watches = getActiveWatches(chatId);
22544
+ if (watches.length === 0) {
22545
+ await channel.sendText(chatId, "No active watches.", { parseMode: "plain" });
22546
+ } else {
22547
+ const lines = ["Active watches:", ""];
22548
+ for (const w of watches) {
22549
+ const expiry = w.expiresAt ? ` (until ${formatLocalDateTime(w.expiresAt)})` : "";
22550
+ lines.push(`#${w.id}: ${w.description}${expiry}`);
22551
+ }
22552
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
22553
+ }
22554
+ return;
22555
+ }
22556
+ default:
22557
+ const watchDesc = commandArgs.trim();
22558
+ const watchId = addHeartbeatWatch(chatId, watchDesc);
22559
+ await channel.sendText(chatId, `\u2705 Watch #${watchId} added:
22560
+ ${watchDesc}`, { parseMode: "plain" });
22561
+ return;
22562
+ }
22563
+ }
22085
22564
  async function handleAgentsCommand(chatId, commandArgs, msg, channel) {
22086
22565
  if (commandArgs?.startsWith("mode")) {
22087
22566
  const modeArg = commandArgs.slice(5).trim().toLowerCase();
@@ -22644,6 +23123,9 @@ async function handleCommand(msg, channel) {
22644
23123
  case "heartbeat":
22645
23124
  await handleHeartbeatCommand(chatId, commandArgs, msg, channel);
22646
23125
  break;
23126
+ case "watch":
23127
+ await handleWatchCommand(chatId, commandArgs, msg, channel);
23128
+ break;
22647
23129
  case "agents":
22648
23130
  await handleAgentsCommand(chatId, commandArgs, msg, channel);
22649
23131
  break;
@@ -23542,8 +24024,8 @@ ${rotationNote}`, { parseMode: "html" });
23542
24024
  if (action === "toggle") {
23543
24025
  const backend2 = parts[2];
23544
24026
  const model2 = parts[3];
23545
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
23546
- const toggleAdapter = getAdapter4(backend2);
24027
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
24028
+ const toggleAdapter = getAdapter5(backend2);
23547
24029
  const label2 = toggleAdapter.availableModels[model2]?.label ?? model2;
23548
24030
  const { toggleParticipant: toggleParticipant2, buildSelectKeyboard: buildSelectKeyboard2, hasPendingCouncil: hasPendingCouncil2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports));
23549
24031
  if (!hasPendingCouncil2(chatId)) {
@@ -23793,11 +24275,12 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
23793
24275
  if (rest === "on") {
23794
24276
  setHeartbeatConfig(chatId, { enabled: true });
23795
24277
  startHeartbeatForChat(chatId);
23796
- await sendHeartbeatKeyboard(chatId, channel);
24278
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
23797
24279
  } else if (rest === "off") {
23798
24280
  setHeartbeatConfig(chatId, { enabled: false });
23799
24281
  stopHeartbeatForChat(chatId);
23800
- await sendHeartbeatKeyboard(chatId, channel);
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" });
24283
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
23801
24284
  } else if (rest.startsWith("interval:")) {
23802
24285
  const min = parseInt(rest.slice(9), 10);
23803
24286
  if (isNaN(min) || min < 1) return;
@@ -23806,7 +24289,55 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
23806
24289
  stopHeartbeatForChat(chatId);
23807
24290
  const hbConf = getHeartbeatConfig(chatId);
23808
24291
  if (hbConf?.enabled) startHeartbeatForChat(chatId);
23809
- await sendHeartbeatKeyboard(chatId, channel);
24292
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24293
+ } else if (rest.startsWith("backend:")) {
24294
+ const bid = rest.slice(8);
24295
+ if (bid === "default") {
24296
+ updateHeartbeatField(chatId, "backend", null);
24297
+ updateHeartbeatField(chatId, "model", null);
24298
+ } else {
24299
+ updateHeartbeatField(chatId, "backend", bid);
24300
+ updateHeartbeatField(chatId, "model", null);
24301
+ }
24302
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24303
+ } else if (rest.startsWith("model:")) {
24304
+ const model2 = rest.slice(6);
24305
+ if (model2 === "default") {
24306
+ updateHeartbeatField(chatId, "model", null);
24307
+ } else {
24308
+ updateHeartbeatField(chatId, "model", model2);
24309
+ }
24310
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24311
+ } else if (rest === "thinking") {
24312
+ const config2 = getHeartbeatConfig(chatId);
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);
24318
+ } else if (rest === "run") {
24319
+ await channel.sendText(chatId, "\u23F3 Running heartbeat check...", { parseMode: "plain" });
24320
+ try {
24321
+ await runHeartbeatNow(chatId);
24322
+ await channel.sendText(chatId, "\u2705 Heartbeat check complete.", { parseMode: "plain" });
24323
+ } catch (err) {
24324
+ await channel.sendText(chatId, `\u274C Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, { parseMode: "plain" });
24325
+ }
24326
+ } else if (rest === "watches" || rest === "addwatch") {
24327
+ 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
+ } else if (rest.startsWith("fb:add:")) {
24329
+ const bid = rest.slice(7);
24330
+ const config2 = getHeartbeatConfig(chatId);
24331
+ const current = parseHeartbeatFallbacks(config2?.fallbacks ?? null);
24332
+ if (!current.some((f) => f.backend === bid)) {
24333
+ current.push({ backend: bid });
24334
+ updateHeartbeatField(chatId, "fallbacks", JSON.stringify(current));
24335
+ }
24336
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24337
+ } else if (rest === "fb:clear") {
24338
+ updateHeartbeatField(chatId, "fallbacks", null);
24339
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24340
+ } else if (rest === "noop") {
23810
24341
  }
23811
24342
  return;
23812
24343
  } else if (data.startsWith("newchat:")) {
@@ -25636,18 +26167,18 @@ var init_wrap_backend = __esm({
25636
26167
  });
25637
26168
 
25638
26169
  // src/agents/runners/config-loader.ts
25639
- import { readFileSync as readFileSync14, readdirSync as readdirSync14, existsSync as existsSync23, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
26170
+ import { readFileSync as readFileSync15, readdirSync as readdirSync14, existsSync as existsSync24, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
25640
26171
  import { join as join27 } from "path";
25641
- import { execFileSync as execFileSync2 } from "child_process";
26172
+ import { execFileSync as execFileSync3 } from "child_process";
25642
26173
  function resolveExecutable2(config2) {
25643
- if (existsSync23(config2.executable)) return config2.executable;
26174
+ if (existsSync24(config2.executable)) return config2.executable;
25644
26175
  try {
25645
- return execFileSync2("which", [config2.executable], { encoding: "utf-8" }).trim();
26176
+ return execFileSync3("which", [config2.executable], { encoding: "utf-8" }).trim();
25646
26177
  } catch {
25647
26178
  }
25648
26179
  for (const fallback of config2.executableFallbacks ?? []) {
25649
26180
  const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
25650
- if (existsSync23(resolved)) return resolved;
26181
+ if (existsSync24(resolved)) return resolved;
25651
26182
  }
25652
26183
  return config2.executable;
25653
26184
  }
@@ -25778,7 +26309,7 @@ function configToRunner(config2) {
25778
26309
  }
25779
26310
  function loadRunnerConfig(filePath) {
25780
26311
  try {
25781
- const content = readFileSync14(filePath, "utf-8");
26312
+ const content = readFileSync15(filePath, "utf-8");
25782
26313
  return JSON.parse(content);
25783
26314
  } catch (err) {
25784
26315
  warn(`[runners] Failed to load config ${filePath}: ${err}`);
@@ -25786,7 +26317,7 @@ function loadRunnerConfig(filePath) {
25786
26317
  }
25787
26318
  }
25788
26319
  function loadAllRunnerConfigs() {
25789
- if (!existsSync23(RUNNERS_PATH)) {
26320
+ if (!existsSync24(RUNNERS_PATH)) {
25790
26321
  mkdirSync10(RUNNERS_PATH, { recursive: true });
25791
26322
  return [];
25792
26323
  }
@@ -25814,9 +26345,9 @@ function registerConfigRunners() {
25814
26345
  return count;
25815
26346
  }
25816
26347
  function watchRunnerConfigs(onChange) {
25817
- if (!existsSync23(RUNNERS_PATH)) return;
26348
+ if (!existsSync24(RUNNERS_PATH)) return;
25818
26349
  for (const prev of watchedFiles) {
25819
- if (!existsSync23(prev)) {
26350
+ if (!existsSync24(prev)) {
25820
26351
  unwatchFile(prev);
25821
26352
  watchedFiles.delete(prev);
25822
26353
  }
@@ -26348,6 +26879,7 @@ var init_telegram2 = __esm({
26348
26879
  { command: "model_signature", description: "Toggle model+thinking signature on responses" },
26349
26880
  { command: "imagine", description: "Generate an image from a prompt" },
26350
26881
  { command: "heartbeat", description: "Configure proactive heartbeat" },
26882
+ { command: "watch", description: "Manage heartbeat awareness tasks" },
26351
26883
  { command: "chats", description: "Manage multi-chat aliases" },
26352
26884
  { command: "intent", description: "Test intent classifier on a message" },
26353
26885
  { command: "evolve", description: "Self-learning & evolution controls" },
@@ -26911,19 +27443,19 @@ var init_telegram2 = __esm({
26911
27443
  });
26912
27444
 
26913
27445
  // src/skills/bootstrap.ts
26914
- import { existsSync as existsSync24 } from "fs";
27446
+ import { existsSync as existsSync25 } from "fs";
26915
27447
  import { readdir as readdir5, readFile as readFile8, writeFile as writeFile5, copyFile } from "fs/promises";
26916
27448
  import { join as join28, dirname as dirname5 } from "path";
26917
27449
  import { fileURLToPath as fileURLToPath2 } from "url";
26918
27450
  async function copyAgentManifestSkills() {
26919
- if (!existsSync24(PKG_SKILLS)) return;
27451
+ if (!existsSync25(PKG_SKILLS)) return;
26920
27452
  try {
26921
27453
  const entries = await readdir5(PKG_SKILLS, { withFileTypes: true });
26922
27454
  for (const entry of entries) {
26923
27455
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
26924
27456
  const src = join28(PKG_SKILLS, entry.name);
26925
27457
  const dest = join28(SKILLS_PATH, entry.name);
26926
- if (existsSync24(dest)) continue;
27458
+ if (existsSync25(dest)) continue;
26927
27459
  await copyFile(src, dest);
26928
27460
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
26929
27461
  }
@@ -26934,7 +27466,7 @@ async function copyAgentManifestSkills() {
26934
27466
  async function bootstrapSkills() {
26935
27467
  await copyAgentManifestSkills();
26936
27468
  const usmDir = join28(SKILLS_PATH, USM_DIR_NAME);
26937
- if (existsSync24(usmDir)) return;
27469
+ if (existsSync25(usmDir)) return;
26938
27470
  try {
26939
27471
  const entries = await readdir5(SKILLS_PATH);
26940
27472
  const dirs = entries.filter((e) => !e.startsWith("."));
@@ -26958,7 +27490,7 @@ async function bootstrapSkills() {
26958
27490
  }
26959
27491
  async function patchUsmForCcClaw(usmDir) {
26960
27492
  const skillPath = join28(usmDir, "SKILL.md");
26961
- if (!existsSync24(skillPath)) return;
27493
+ if (!existsSync25(skillPath)) return;
26962
27494
  try {
26963
27495
  let content = await readFile8(skillPath, "utf-8");
26964
27496
  let patched = false;
@@ -27226,13 +27758,13 @@ __export(ai_skill_exports, {
27226
27758
  generateAiSkill: () => generateAiSkill,
27227
27759
  installAiSkill: () => installAiSkill
27228
27760
  });
27229
- import { existsSync as existsSync25, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
27761
+ import { existsSync as existsSync26, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
27230
27762
  import { join as join29 } from "path";
27231
27763
  import { homedir as homedir9 } from "os";
27232
27764
  function generateAiSkill() {
27233
27765
  const version = VERSION;
27234
27766
  let systemState = "";
27235
- if (existsSync25(DB_PATH)) {
27767
+ if (existsSync26(DB_PATH)) {
27236
27768
  try {
27237
27769
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
27238
27770
  const readDb = openDatabaseReadOnly2();
@@ -27513,7 +28045,7 @@ cc-claw voice set off # Disable voice responses
27513
28045
  # Requires GEMINI_API_KEY in ~/.cc-claw/.env
27514
28046
  \`\`\`
27515
28047
 
27516
- ### Heartbeat
28048
+ ### Heartbeat & Watches (Proactive Monitoring)
27517
28049
  \`\`\`bash
27518
28050
  cc-claw heartbeat get --json # Show heartbeat config
27519
28051
  cc-claw heartbeat set on # Enable proactive awareness
@@ -27522,6 +28054,20 @@ cc-claw heartbeat set interval 30m # Set check interval
27522
28054
  cc-claw heartbeat set hours 9-22 # Set active hours
27523
28055
  \`\`\`
27524
28056
 
28057
+ **Heartbeat** runs a periodic awareness cycle that checks system health and user-defined watches.
28058
+ It supports delivery parity: custom backend, model, fallback chain, thinking level, and delivery target \u2014 all configured via the interactive \`/heartbeat\` keyboard in Telegram.
28059
+ When nothing needs attention, the AI responds with \`HEARTBEAT_OK\` and no message is delivered.
28060
+
28061
+ **Watches** are AI-executable awareness tasks checked during each heartbeat cycle:
28062
+ \`\`\`
28063
+ /watch add Check my email for urgent messages # Persistent watch
28064
+ /watch add Monitor Ollama health for 24h # Time-limited watch
28065
+ /watch list # List active watches
28066
+ /watch remove <id> # Remove a watch
28067
+ \`\`\`
28068
+
28069
+ **\u26A0\uFE0F CRITICAL:** Heartbeat and watch configuration MUST be done via \`/heartbeat\` and \`/watch\` commands. NEVER modify \`chat_heartbeat\` or \`heartbeat_watches\` database tables directly.
28070
+
27525
28071
  ### Summarizer
27526
28072
  \`\`\`bash
27527
28073
  cc-claw summarizer get --json # Current summarizer config
@@ -27692,7 +28238,7 @@ var index_exports = {};
27692
28238
  __export(index_exports, {
27693
28239
  main: () => main
27694
28240
  });
27695
- import { mkdirSync as mkdirSync12, existsSync as existsSync26, renameSync as renameSync2, statSync as statSync8, readFileSync as readFileSync16 } from "fs";
28241
+ import { mkdirSync as mkdirSync12, existsSync as existsSync27, renameSync as renameSync2, statSync as statSync9, readFileSync as readFileSync17 } from "fs";
27696
28242
  import { join as join30 } from "path";
27697
28243
  import dotenv from "dotenv";
27698
28244
  function migrateLayout() {
@@ -27706,7 +28252,7 @@ function migrateLayout() {
27706
28252
  [join30(CC_CLAW_HOME, "cc-claw.error.log.1"), join30(LOGS_PATH, "cc-claw.error.log.1")]
27707
28253
  ];
27708
28254
  for (const [from, to] of moves) {
27709
- if (existsSync26(from) && !existsSync26(to)) {
28255
+ if (existsSync27(from) && !existsSync27(to)) {
27710
28256
  try {
27711
28257
  renameSync2(from, to);
27712
28258
  } catch {
@@ -27717,7 +28263,7 @@ function migrateLayout() {
27717
28263
  function rotateLogs() {
27718
28264
  for (const file of [LOG_PATH, ERROR_LOG_PATH]) {
27719
28265
  try {
27720
- const { size } = statSync8(file);
28266
+ const { size } = statSync9(file);
27721
28267
  if (size > LOG_MAX_BYTES) {
27722
28268
  const archivePath = `${file}.1`;
27723
28269
  try {
@@ -27735,7 +28281,7 @@ async function main() {
27735
28281
  let version = "unknown";
27736
28282
  try {
27737
28283
  const pkgPath = new URL("../package.json", import.meta.url);
27738
- version = JSON.parse(readFileSync16(pkgPath, "utf-8")).version;
28284
+ version = JSON.parse(readFileSync17(pkgPath, "utf-8")).version;
27739
28285
  } catch {
27740
28286
  }
27741
28287
  log(`[cc-claw] Starting v${version}`);
@@ -27749,11 +28295,11 @@ async function main() {
27749
28295
  } catch {
27750
28296
  }
27751
28297
  try {
27752
- const { getAdapter: getAdapter4, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
28298
+ const { getAdapter: getAdapter5, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
27753
28299
  await probeBackendAvailability2();
27754
- const claude = getAdapter4("claude");
28300
+ const claude = getAdapter5("claude");
27755
28301
  if ("getAuthMode" in claude) claude.getAuthMode();
27756
- const cursor = getAdapter4("cursor");
28302
+ const cursor = getAdapter5("cursor");
27757
28303
  if ("probeTier" in cursor) cursor.probeTier();
27758
28304
  } catch {
27759
28305
  }
@@ -27773,8 +28319,8 @@ async function main() {
27773
28319
  }
27774
28320
  if (modelCount > 0) {
27775
28321
  try {
27776
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
27777
- const adapter = getAdapter4("ollama");
28322
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
28323
+ const adapter = getAdapter5("ollama");
27778
28324
  if ("refreshModelCatalog" in adapter) {
27779
28325
  adapter.refreshModelCatalog();
27780
28326
  }
@@ -27855,10 +28401,10 @@ async function main() {
27855
28401
  });
27856
28402
  log("[cc-claw] Agent orchestrator initialized");
27857
28403
  try {
27858
- const { execSync: execSync6 } = await import("child_process");
27859
- const codexPath = execSync6("which codex", { encoding: "utf-8", timeout: 5e3 }).trim();
28404
+ const { execSync: execSync7 } = await import("child_process");
28405
+ const codexPath = execSync7("which codex", { encoding: "utf-8", timeout: 5e3 }).trim();
27860
28406
  if (codexPath) {
27861
- const features = execSync6("codex features list", { encoding: "utf-8", timeout: 1e4 });
28407
+ const features = execSync7("codex features list", { encoding: "utf-8", timeout: 1e4 });
27862
28408
  if (/multi_agent\s+\S+\s+false/.test(features)) {
27863
28409
  warn("[cc-claw] Codex multi_agent feature is disabled \u2014 native sub-agent detection will not work. Run: codex features enable multi_agent");
27864
28410
  }
@@ -27986,10 +28532,10 @@ var init_index = __esm({
27986
28532
  init_health3();
27987
28533
  init_image_gen();
27988
28534
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SESSION_LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
27989
- if (!existsSync26(dir)) mkdirSync12(dir, { recursive: true });
28535
+ if (!existsSync27(dir)) mkdirSync12(dir, { recursive: true });
27990
28536
  }
27991
28537
  migrateLayout();
27992
- if (existsSync26(ENV_PATH)) {
28538
+ if (existsSync27(ENV_PATH)) {
27993
28539
  dotenv.config({ path: ENV_PATH });
27994
28540
  } else {
27995
28541
  console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
@@ -28010,12 +28556,12 @@ __export(api_client_exports, {
28010
28556
  apiPost: () => apiPost,
28011
28557
  isDaemonRunning: () => isDaemonRunning
28012
28558
  });
28013
- import { readFileSync as readFileSync17, existsSync as existsSync27 } from "fs";
28559
+ import { readFileSync as readFileSync18, existsSync as existsSync28 } from "fs";
28014
28560
  import { request as httpRequest, Agent } from "http";
28015
28561
  function getToken() {
28016
28562
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
28017
28563
  try {
28018
- if (existsSync27(TOKEN_PATH)) return readFileSync17(TOKEN_PATH, "utf-8").trim();
28564
+ if (existsSync28(TOKEN_PATH)) return readFileSync18(TOKEN_PATH, "utf-8").trim();
28019
28565
  } catch {
28020
28566
  }
28021
28567
  return null;
@@ -28114,8 +28660,8 @@ __export(service_exports2, {
28114
28660
  serviceStatus: () => serviceStatus,
28115
28661
  uninstallService: () => uninstallService
28116
28662
  });
28117
- import { existsSync as existsSync28, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
28118
- import { execFileSync as execFileSync3, execSync as execSync4 } from "child_process";
28663
+ import { existsSync as existsSync29, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
28664
+ import { execFileSync as execFileSync4, execSync as execSync5 } from "child_process";
28119
28665
  import { homedir as homedir10, platform } from "os";
28120
28666
  import { join as join31, dirname as dirname6 } from "path";
28121
28667
  function xmlEscape(s) {
@@ -28123,10 +28669,10 @@ function xmlEscape(s) {
28123
28669
  }
28124
28670
  function resolveExecutable3(name) {
28125
28671
  try {
28126
- return execFileSync3("which", [name], { encoding: "utf-8" }).trim();
28672
+ return execFileSync4("which", [name], { encoding: "utf-8" }).trim();
28127
28673
  } catch {
28128
28674
  const fallback = process.argv[1];
28129
- if (fallback && existsSync28(fallback)) return fallback;
28675
+ if (fallback && existsSync29(fallback)) return fallback;
28130
28676
  throw new Error(`Cannot find '${name}' executable. Install globally: npm install -g cc-claw`);
28131
28677
  }
28132
28678
  }
@@ -28141,7 +28687,7 @@ function getPathDirs() {
28141
28687
  "/bin"
28142
28688
  ]);
28143
28689
  try {
28144
- const prefix = execSync4("npm config get prefix", { encoding: "utf-8" }).trim();
28690
+ const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
28145
28691
  if (prefix) dirs.add(join31(prefix, "bin"));
28146
28692
  } catch {
28147
28693
  }
@@ -28201,26 +28747,26 @@ function generatePlist() {
28201
28747
  }
28202
28748
  function installMacOS() {
28203
28749
  const agentsDir = dirname6(PLIST_PATH);
28204
- if (!existsSync28(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
28205
- if (!existsSync28(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
28206
- if (existsSync28(PLIST_PATH)) {
28750
+ if (!existsSync29(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
28751
+ if (!existsSync29(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
28752
+ if (existsSync29(PLIST_PATH)) {
28207
28753
  try {
28208
- execFileSync3("launchctl", ["unload", PLIST_PATH]);
28754
+ execFileSync4("launchctl", ["unload", PLIST_PATH]);
28209
28755
  } catch {
28210
28756
  }
28211
28757
  }
28212
28758
  writeFileSync9(PLIST_PATH, generatePlist());
28213
28759
  console.log(` Installed: ${PLIST_PATH}`);
28214
- execFileSync3("launchctl", ["load", PLIST_PATH]);
28760
+ execFileSync4("launchctl", ["load", PLIST_PATH]);
28215
28761
  console.log(" Service loaded and starting.");
28216
28762
  }
28217
28763
  function uninstallMacOS() {
28218
- if (!existsSync28(PLIST_PATH)) {
28764
+ if (!existsSync29(PLIST_PATH)) {
28219
28765
  console.log(" No service found to uninstall.");
28220
28766
  return;
28221
28767
  }
28222
28768
  try {
28223
- execFileSync3("launchctl", ["unload", PLIST_PATH]);
28769
+ execFileSync4("launchctl", ["unload", PLIST_PATH]);
28224
28770
  } catch {
28225
28771
  }
28226
28772
  unlinkSync8(PLIST_PATH);
@@ -28246,7 +28792,7 @@ async function getUptimeFromDaemon() {
28246
28792
  }
28247
28793
  function statusMacOS() {
28248
28794
  try {
28249
- const out = execFileSync3("launchctl", ["list"], { encoding: "utf-8" });
28795
+ const out = execFileSync4("launchctl", ["list"], { encoding: "utf-8" });
28250
28796
  const line = out.split("\n").find((l) => l.includes("cc-claw"));
28251
28797
  if (line) {
28252
28798
  const parts = line.trim().split(/\s+/);
@@ -28290,42 +28836,42 @@ WantedBy=default.target
28290
28836
  `;
28291
28837
  }
28292
28838
  function installLinux() {
28293
- if (!existsSync28(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
28294
- if (!existsSync28(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
28839
+ if (!existsSync29(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
28840
+ if (!existsSync29(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
28295
28841
  writeFileSync9(UNIT_PATH, generateUnit());
28296
28842
  console.log(` Installed: ${UNIT_PATH}`);
28297
- execFileSync3("systemctl", ["--user", "daemon-reload"]);
28298
- execFileSync3("systemctl", ["--user", "enable", "cc-claw"]);
28299
- execFileSync3("systemctl", ["--user", "start", "cc-claw"]);
28843
+ execFileSync4("systemctl", ["--user", "daemon-reload"]);
28844
+ execFileSync4("systemctl", ["--user", "enable", "cc-claw"]);
28845
+ execFileSync4("systemctl", ["--user", "start", "cc-claw"]);
28300
28846
  console.log(" Service enabled and started.");
28301
28847
  }
28302
28848
  function uninstallLinux() {
28303
- if (!existsSync28(UNIT_PATH)) {
28849
+ if (!existsSync29(UNIT_PATH)) {
28304
28850
  console.log(" No service found to uninstall.");
28305
28851
  return;
28306
28852
  }
28307
28853
  try {
28308
- execFileSync3("systemctl", ["--user", "stop", "cc-claw"]);
28854
+ execFileSync4("systemctl", ["--user", "stop", "cc-claw"]);
28309
28855
  } catch {
28310
28856
  }
28311
28857
  try {
28312
- execFileSync3("systemctl", ["--user", "disable", "cc-claw"]);
28858
+ execFileSync4("systemctl", ["--user", "disable", "cc-claw"]);
28313
28859
  } catch {
28314
28860
  }
28315
28861
  unlinkSync8(UNIT_PATH);
28316
- execFileSync3("systemctl", ["--user", "daemon-reload"]);
28862
+ execFileSync4("systemctl", ["--user", "daemon-reload"]);
28317
28863
  console.log(" Service uninstalled.");
28318
28864
  }
28319
28865
  function statusLinux() {
28320
28866
  try {
28321
- const out = execSync4("systemctl --user is-active cc-claw", { encoding: "utf-8" }).trim();
28867
+ const out = execSync5("systemctl --user is-active cc-claw", { encoding: "utf-8" }).trim();
28322
28868
  console.log(` Service is ${out}.`);
28323
28869
  } catch {
28324
28870
  console.log(" Not running or not installed.");
28325
28871
  }
28326
28872
  }
28327
28873
  function installService() {
28328
- if (!existsSync28(join31(CC_CLAW_HOME, ".env"))) {
28874
+ if (!existsSync29(join31(CC_CLAW_HOME, ".env"))) {
28329
28875
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
28330
28876
  console.error(" Run 'cc-claw setup' before installing the service.");
28331
28877
  process.exitCode = 1;
@@ -28466,18 +29012,18 @@ __export(daemon_exports, {
28466
29012
  restartService: () => restartService,
28467
29013
  stopService: () => stopService
28468
29014
  });
28469
- import { execSync as execSync5 } from "child_process";
29015
+ import { execSync as execSync6 } from "child_process";
28470
29016
  import { platform as platform2 } from "os";
28471
29017
  async function stopService() {
28472
29018
  const os2 = platform2();
28473
29019
  try {
28474
29020
  if (os2 === "darwin") {
28475
- execSync5("launchctl stop com.cc-claw");
29021
+ execSync6("launchctl stop com.cc-claw");
28476
29022
  console.log(`
28477
29023
  ${success("Daemon stopped.")}
28478
29024
  `);
28479
29025
  } else if (os2 === "linux") {
28480
- execSync5("systemctl --user stop cc-claw");
29026
+ execSync6("systemctl --user stop cc-claw");
28481
29027
  console.log(`
28482
29028
  ${success("Daemon stopped.")}
28483
29029
  `);
@@ -28495,13 +29041,13 @@ async function restartService() {
28495
29041
  const os2 = platform2();
28496
29042
  try {
28497
29043
  if (os2 === "darwin") {
28498
- const uid = process.getuid?.() ?? execSync5("id -u", { encoding: "utf-8" }).trim();
28499
- execSync5(`launchctl kickstart -k gui/${uid}/com.cc-claw`);
29044
+ const uid = process.getuid?.() ?? execSync6("id -u", { encoding: "utf-8" }).trim();
29045
+ execSync6(`launchctl kickstart -k gui/${uid}/com.cc-claw`);
28500
29046
  console.log(`
28501
29047
  ${success("Daemon restarted.")}
28502
29048
  `);
28503
29049
  } else if (os2 === "linux") {
28504
- execSync5("systemctl --user restart cc-claw");
29050
+ execSync6("systemctl --user restart cc-claw");
28505
29051
  console.log(`
28506
29052
  ${success("Daemon restarted.")}
28507
29053
  `);
@@ -28523,13 +29069,13 @@ var init_daemon = __esm({
28523
29069
  });
28524
29070
 
28525
29071
  // src/cli/resolve-chat.ts
28526
- import { readFileSync as readFileSync19 } from "fs";
29072
+ import { readFileSync as readFileSync20 } from "fs";
28527
29073
  function resolveChatId2(globalOpts) {
28528
29074
  const explicit = globalOpts.chat;
28529
29075
  if (explicit) return explicit;
28530
29076
  if (_cachedDefault) return _cachedDefault;
28531
29077
  try {
28532
- const content = readFileSync19(ENV_PATH, "utf-8");
29078
+ const content = readFileSync20(ENV_PATH, "utf-8");
28533
29079
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
28534
29080
  if (match) {
28535
29081
  _cachedDefault = match[1].split(",")[0].trim();
@@ -28553,13 +29099,13 @@ var status_exports = {};
28553
29099
  __export(status_exports, {
28554
29100
  statusCommand: () => statusCommand
28555
29101
  });
28556
- import { existsSync as existsSync29, statSync as statSync9 } from "fs";
29102
+ import { existsSync as existsSync30, statSync as statSync10 } from "fs";
28557
29103
  async function statusCommand(globalOpts, localOpts) {
28558
29104
  try {
28559
29105
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28560
29106
  const readDb = openDatabaseReadOnly2();
28561
29107
  const chatId = resolveChatId2(globalOpts);
28562
- const { getAdapterForChat: getAdapterForChat2, getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
29108
+ const { getAdapterForChat: getAdapterForChat3, getAdapter: getAdapter5, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
28563
29109
  let backend2 = null;
28564
29110
  let modelName = "not set";
28565
29111
  let contextMax = 2e5;
@@ -28567,7 +29113,7 @@ async function statusCommand(globalOpts, localOpts) {
28567
29113
  const backendRow = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
28568
29114
  if (backendRow) {
28569
29115
  try {
28570
- const a = getAdapter4(backendRow.backend);
29116
+ const a = getAdapter5(backendRow.backend);
28571
29117
  backend2 = { id: a.id, displayName: a.displayName };
28572
29118
  } catch {
28573
29119
  backend2 = { id: backendRow.backend, displayName: backendRow.backend };
@@ -28580,7 +29126,7 @@ async function statusCommand(globalOpts, localOpts) {
28580
29126
  if (modelRow) modelName = modelRow.model;
28581
29127
  else if (backend2) {
28582
29128
  try {
28583
- modelName = getAdapter4(backend2.id).defaultModel;
29129
+ modelName = getAdapter5(backend2.id).defaultModel;
28584
29130
  } catch {
28585
29131
  }
28586
29132
  }
@@ -28593,7 +29139,7 @@ async function statusCommand(globalOpts, localOpts) {
28593
29139
  const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
28594
29140
  const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
28595
29141
  const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
28596
- const dbStat = existsSync29(DB_PATH) ? statSync9(DB_PATH) : null;
29142
+ const dbStat = existsSync30(DB_PATH) ? statSync10(DB_PATH) : null;
28597
29143
  let daemonRunning = false;
28598
29144
  let daemonInfo = {};
28599
29145
  try {
@@ -28705,13 +29251,13 @@ __export(doctor_exports, {
28705
29251
  doctorCommand: () => doctorCommand,
28706
29252
  doctorErrors: () => doctorErrors
28707
29253
  });
28708
- import { existsSync as existsSync30, statSync as statSync10, accessSync, readFileSync as readFileSync20, constants } from "fs";
28709
- import { execFileSync as execFileSync4 } from "child_process";
29254
+ import { existsSync as existsSync31, accessSync, constants } from "fs";
29255
+ import { execFileSync as execFileSync5 } from "child_process";
28710
29256
  async function doctorCommand(globalOpts, localOpts) {
28711
29257
  const checks = [];
28712
- if (existsSync30(DB_PATH)) {
28713
- const size = statSync10(DB_PATH).size;
28714
- checks.push({ name: "Database", status: "ok", message: `${DB_PATH} (${(size / 1024).toFixed(0)}KB)` });
29258
+ const dbChecks = checkDatabase();
29259
+ checks.push(...dbChecks);
29260
+ if (existsSync31(DB_PATH)) {
28715
29261
  try {
28716
29262
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28717
29263
  const readDb = openDatabaseReadOnly2();
@@ -28736,27 +29282,13 @@ async function doctorCommand(globalOpts, localOpts) {
28736
29282
  } catch (err) {
28737
29283
  checks.push({ name: "Database health", status: "error", message: err.message });
28738
29284
  }
28739
- } else {
28740
- checks.push({ name: "Database", status: "error", message: `Not found at ${DB_PATH}`, fix: "cc-claw setup" });
28741
29285
  }
28742
- if (existsSync30(ENV_PATH)) {
29286
+ if (existsSync31(ENV_PATH)) {
28743
29287
  checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
28744
29288
  } else {
28745
29289
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
28746
29290
  }
28747
- const CLI_BINARIES = { claude: "claude", gemini: "gemini", codex: "codex", cursor: "agent" };
28748
- let installedBackends = 0;
28749
- for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
28750
- try {
28751
- const path = execFileSync4("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
28752
- checks.push({ name: `${label2} CLI`, status: "ok", message: path });
28753
- installedBackends++;
28754
- } catch {
28755
- }
28756
- }
28757
- if (installedBackends === 0) {
28758
- checks.push({ name: "Backend CLIs", status: "error", message: "No backend CLIs found. Install at least one (claude, gemini, codex, or cursor agent)." });
28759
- }
29291
+ checks.push(...checkBackendCLIs());
28760
29292
  try {
28761
29293
  const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
28762
29294
  const running = await isDaemonRunning2();
@@ -28787,14 +29319,14 @@ async function doctorCommand(globalOpts, localOpts) {
28787
29319
  checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
28788
29320
  }
28789
29321
  try {
28790
- const latest = execFileSync4("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
29322
+ const latest = execFileSync5("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
28791
29323
  if (latest && latest !== VERSION) {
28792
29324
  checks.push({ name: "Update available", status: "warning", message: `v${latest} available (current: v${VERSION})`, fix: "npm install -g cc-claw@latest" });
28793
29325
  }
28794
29326
  } catch {
28795
29327
  }
28796
29328
  const tokenPath = `${DATA_PATH}/api-token`;
28797
- if (existsSync30(tokenPath)) {
29329
+ if (existsSync31(tokenPath)) {
28798
29330
  try {
28799
29331
  accessSync(tokenPath, constants.R_OK);
28800
29332
  checks.push({ name: "API token", status: "ok", message: "token file readable" });
@@ -28804,125 +29336,28 @@ async function doctorCommand(globalOpts, localOpts) {
28804
29336
  } else {
28805
29337
  checks.push({ name: "API token", status: "warning", message: "no token file (daemon not started?)", fix: "cc-claw service start" });
28806
29338
  }
28807
- try {
28808
- const { execSync: execSync6 } = await import("child_process");
28809
- const dfOutput = execSync6("df -k " + DATA_PATH, { encoding: "utf-8" });
28810
- const lines = dfOutput.trim().split("\n");
28811
- if (lines.length >= 2) {
28812
- const parts = lines[1].split(/\s+/);
28813
- const availKB = parseInt(parts[3], 10);
28814
- if (availKB < 1e5) {
28815
- checks.push({ name: "Disk space", status: "warning", message: `${(availKB / 1024).toFixed(0)}MB available` });
28816
- } else {
28817
- checks.push({ name: "Disk space", status: "ok", message: `${(availKB / 1024 / 1024).toFixed(1)}GB available` });
28818
- }
28819
- }
28820
- } catch {
28821
- }
28822
- if (existsSync30(ERROR_LOG_PATH)) {
28823
- try {
28824
- const { readFileSync: readFileSync28 } = await import("fs");
28825
- const logContent = readFileSync28(ERROR_LOG_PATH, "utf-8");
28826
- const recentLines = logContent.split("\n").filter(Boolean).slice(-500);
28827
- const last24h = Date.now() - 864e5;
28828
- const recentErrors = recentLines.filter((line) => {
28829
- const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:]+)/);
28830
- if (match) return (/* @__PURE__ */ new Date(`${match[1]}T${match[2]}`)).getTime() > last24h;
28831
- return false;
28832
- });
28833
- if (recentErrors.length > 0) {
28834
- let rate429 = 0;
28835
- let contentSilence = 0;
28836
- let spawnTimeout = 0;
28837
- const otherLines = [];
28838
- for (const line of recentErrors) {
28839
- if (/429|rate.?limit/i.test(line)) rate429++;
28840
- else if (/content silence/i.test(line)) contentSilence++;
28841
- else if (/spawn timeout|timeout after \d+s/i.test(line)) spawnTimeout++;
28842
- else otherLines.push(line);
28843
- }
28844
- const viewFix = "cc-claw logs --error to view details";
28845
- const clearFix = "cc-claw doctor --fix to clear stale errors";
28846
- if (rate429 > 10) {
28847
- checks.push({ name: "Telegram rate limits", status: "error", message: `${rate429} rate-limit (429) errors in last 24h \u2014 message delivery blocked`, fix: `${viewFix}, ${clearFix}` });
28848
- } else if (rate429 > 0) {
28849
- checks.push({ name: "Telegram rate limits", status: "warning", message: `${rate429} rate-limit (429) errors in last 24h`, fix: `${viewFix}, ${clearFix}` });
28850
- }
28851
- if (contentSilence > 0) {
28852
- checks.push({ name: "Content silence", status: "warning", message: `${contentSilence} agent silence timeout(s) in last 24h \u2014 API went unresponsive`, fix: `${viewFix}, ${clearFix}` });
28853
- }
28854
- if (spawnTimeout > 0) {
28855
- checks.push({ name: "Spawn timeouts", status: "warning", message: `${spawnTimeout} backend timeout(s) in last 24h`, fix: `${viewFix}, ${clearFix}` });
28856
- }
28857
- if (otherLines.length > 0) {
28858
- const summaries = /* @__PURE__ */ new Map();
28859
- for (const line of otherLines) {
28860
- const body = line.replace(/^\[\d{4}-\d{2}-\d{2}\s+[\d:+-]+\]\s*/, "").slice(0, 120);
28861
- const key = body.slice(0, 80);
28862
- summaries.set(key, (summaries.get(key) ?? 0) + 1);
28863
- }
28864
- const topErrors = [...summaries.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([msg, count]) => count > 1 ? `${msg}\u2026 (\xD7${count})` : `${msg}\u2026`).join("\n ");
28865
- checks.push({ name: "Other errors", status: "warning", message: `${otherLines.length} error(s) in last 24h:
28866
- ${topErrors}`, fix: `${viewFix}, ${clearFix}` });
28867
- }
28868
- if (rate429 === 0 && contentSilence === 0 && spawnTimeout === 0 && otherLines.length === 0) {
28869
- checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
28870
- }
28871
- } else {
28872
- checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
28873
- }
28874
- } catch {
28875
- checks.push({ name: "Error log", status: "ok", message: "no errors logged" });
28876
- }
28877
- } else {
28878
- checks.push({ name: "Error log", status: "ok", message: "no error log file" });
28879
- }
28880
- try {
28881
- const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28882
- const servers = OllamaStore.listServers();
28883
- if (servers.length > 0) {
28884
- const online = servers.filter((s) => s.status === "online");
28885
- const models = OllamaStore.getAvailableModels();
28886
- if (online.length === servers.length) {
28887
- checks.push({
28888
- name: "Ollama",
28889
- status: "ok",
28890
- message: `${online.length} server(s) online, ${models.length} model(s)`
28891
- });
28892
- } else if (online.length > 0) {
28893
- checks.push({
28894
- name: "Ollama",
28895
- status: "warning",
28896
- message: `${online.length}/${servers.length} server(s) online, ${models.length} model(s)`
28897
- });
28898
- } else {
28899
- checks.push({
28900
- name: "Ollama",
28901
- status: "warning",
28902
- message: `${servers.length} server(s) configured, all offline`,
28903
- fix: "Check Ollama is running: ollama serve"
28904
- });
28905
- }
28906
- }
28907
- } catch {
28908
- }
29339
+ const disk = checkDiskSpace();
29340
+ if (disk) checks.push(disk);
29341
+ checks.push(...checkErrorLog());
29342
+ const ollama2 = checkOllamaServers();
29343
+ if (ollama2) checks.push(ollama2);
28909
29344
  if (localOpts.fix) {
28910
29345
  const fixable = checks.filter((c) => c.status !== "ok" && c.fix);
28911
29346
  for (const check of fixable) {
28912
29347
  if (check.name === "Daemon" && check.fix?.includes("service start")) {
28913
29348
  try {
28914
- const { execSync: execSync6 } = await import("child_process");
29349
+ const { execSync: execSync7 } = await import("child_process");
28915
29350
  const os2 = (await import("os")).platform();
28916
29351
  if (os2 === "darwin") {
28917
29352
  try {
28918
- execSync6("launchctl start com.cc-claw");
29353
+ execSync7("launchctl start com.cc-claw");
28919
29354
  check.status = "ok";
28920
29355
  check.message = "restarted";
28921
29356
  } catch {
28922
29357
  }
28923
29358
  } else if (os2 === "linux") {
28924
29359
  try {
28925
- execSync6("systemctl --user start cc-claw");
29360
+ execSync7("systemctl --user start cc-claw");
28926
29361
  check.status = "ok";
28927
29362
  check.message = "restarted";
28928
29363
  } catch {
@@ -28955,9 +29390,9 @@ async function doctorCommand(globalOpts, localOpts) {
28955
29390
  }
28956
29391
  }
28957
29392
  const errorChecks = checks.filter(
28958
- (c) => ["Telegram rate limits", "Content silence", "Spawn timeouts", "Other errors"].includes(c.name) && c.status !== "ok"
29393
+ (c) => ["Rate limits", "Content silence", "Spawn timeouts", "Other errors"].includes(c.name) && c.status !== "ok"
28959
29394
  );
28960
- if (errorChecks.length > 0 && existsSync30(ERROR_LOG_PATH)) {
29395
+ if (errorChecks.length > 0 && existsSync31(ERROR_LOG_PATH)) {
28961
29396
  try {
28962
29397
  const { writeFileSync: writeFileSync13 } = await import("fs");
28963
29398
  writeFileSync13(ERROR_LOG_PATH, "");
@@ -28970,9 +29405,9 @@ async function doctorCommand(globalOpts, localOpts) {
28970
29405
  }
28971
29406
  }
28972
29407
  }
28973
- const errors = checks.filter((c) => c.status === "error").length;
28974
- const warnings = checks.filter((c) => c.status === "warning").length;
28975
- const report = { checks, errors, warnings };
29408
+ const errCount = checks.filter((c) => c.status === "error").length;
29409
+ const warnCount = checks.filter((c) => c.status === "warning").length;
29410
+ const report = { checks, errors: errCount, warnings: warnCount };
28976
29411
  output(report, (d) => {
28977
29412
  const r = d;
28978
29413
  const lines = [
@@ -29003,27 +29438,7 @@ async function doctorCommand(globalOpts, localOpts) {
29003
29438
  lines.push("");
29004
29439
  return lines.join("\n");
29005
29440
  });
29006
- process.exit(errors > 0 ? 1 : warnings > 0 ? 2 : 0);
29007
- }
29008
- function getRecentErrors() {
29009
- if (!existsSync30(ERROR_LOG_PATH)) return null;
29010
- const logContent = readFileSync20(ERROR_LOG_PATH, "utf-8");
29011
- const allLines = logContent.split("\n").filter(Boolean).slice(-500);
29012
- const last24h = Date.now() - 864e5;
29013
- const lines = allLines.filter((line) => {
29014
- const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:+-]+)/);
29015
- if (match) return (/* @__PURE__ */ new Date(`${match[1]}T${match[2]}`)).getTime() > last24h;
29016
- return false;
29017
- });
29018
- if (lines.length === 0) return null;
29019
- const classified = { rate429: [], contentSilence: [], spawnTimeout: [], other: [] };
29020
- for (const line of lines) {
29021
- if (/429|rate.?limit/i.test(line)) classified.rate429.push(line);
29022
- else if (/content silence/i.test(line)) classified.contentSilence.push(line);
29023
- else if (/spawn timeout|timeout after \d+s/i.test(line)) classified.spawnTimeout.push(line);
29024
- else classified.other.push(line);
29025
- }
29026
- return { lines, classified };
29441
+ process.exit(errCount > 0 ? 1 : warnCount > 0 ? 2 : 0);
29027
29442
  }
29028
29443
  function stripTimestamp(line) {
29029
29444
  const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:+-]+)\]\s*(.*)/);
@@ -29038,9 +29453,8 @@ async function doctorErrors(globalOpts) {
29038
29453
  `);
29039
29454
  return;
29040
29455
  }
29041
- const { classified } = result;
29042
- const total = classified.rate429.length + classified.contentSilence.length + classified.spawnTimeout.length + classified.other.length;
29043
- output({ total, classified }, () => {
29456
+ const { rate429, contentSilence, spawnTimeout, other, total } = result;
29457
+ output({ total, classified: result }, () => {
29044
29458
  const lines = [
29045
29459
  "",
29046
29460
  box("Recent Errors (last 24h)"),
@@ -29072,10 +29486,10 @@ async function doctorErrors(globalOpts) {
29072
29486
  }
29073
29487
  lines.push("");
29074
29488
  };
29075
- renderCategory("Telegram rate limits", classified.rate429, 5);
29076
- renderCategory("Content silence timeouts", classified.contentSilence, 5);
29077
- renderCategory("Spawn timeouts", classified.spawnTimeout, 5);
29078
- renderCategory("Other errors", classified.other, 10);
29489
+ renderCategory("Telegram rate limits", rate429, 5);
29490
+ renderCategory("Content silence timeouts", contentSilence, 5);
29491
+ renderCategory("Spawn timeouts", spawnTimeout, 5);
29492
+ renderCategory("Other errors", other, 10);
29079
29493
  if (total === 0) {
29080
29494
  lines.push(` ${success("No errors in last 24h.")}`);
29081
29495
  } else {
@@ -29098,6 +29512,7 @@ var init_doctor = __esm({
29098
29512
  init_format2();
29099
29513
  init_paths();
29100
29514
  init_version();
29515
+ init_checks();
29101
29516
  }
29102
29517
  });
29103
29518
 
@@ -29106,15 +29521,15 @@ var logs_exports = {};
29106
29521
  __export(logs_exports, {
29107
29522
  logsCommand: () => logsCommand
29108
29523
  });
29109
- import { existsSync as existsSync31, readFileSync as readFileSync21, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
29524
+ import { existsSync as existsSync32, readFileSync as readFileSync22, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
29110
29525
  async function logsCommand(opts) {
29111
29526
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
29112
- if (!existsSync31(logFile)) {
29527
+ if (!existsSync32(logFile)) {
29113
29528
  outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
29114
29529
  process.exit(1);
29115
29530
  }
29116
29531
  const maxLines = parseInt(opts.lines ?? "100", 10);
29117
- const content = readFileSync21(logFile, "utf-8");
29532
+ const content = readFileSync22(logFile, "utf-8");
29118
29533
  const allLines = content.split("\n");
29119
29534
  const tailLines = allLines.slice(-maxLines);
29120
29535
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
@@ -29124,7 +29539,7 @@ async function logsCommand(opts) {
29124
29539
  let lastLength = content.length;
29125
29540
  watchFile2(logFile, { interval: 500 }, () => {
29126
29541
  try {
29127
- const newContent = readFileSync21(logFile, "utf-8");
29542
+ const newContent = readFileSync22(logFile, "utf-8");
29128
29543
  if (newContent.length > lastLength) {
29129
29544
  const newPart = newContent.slice(lastLength);
29130
29545
  process.stdout.write(newPart);
@@ -29156,7 +29571,7 @@ __export(session_logs_exports, {
29156
29571
  sessionLogsList: () => sessionLogsList,
29157
29572
  sessionLogsTail: () => sessionLogsTail
29158
29573
  });
29159
- import { readFileSync as readFileSync22, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
29574
+ import { readFileSync as readFileSync23, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
29160
29575
  async function sessionLogsList(opts) {
29161
29576
  const logs = listSessionLogs();
29162
29577
  if (logs.length === 0) {
@@ -29213,12 +29628,12 @@ async function sessionLogsTail(opts) {
29213
29628
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
29214
29629
  let lastLength = 0;
29215
29630
  try {
29216
- lastLength = readFileSync22(targetPath, "utf-8").length;
29631
+ lastLength = readFileSync23(targetPath, "utf-8").length;
29217
29632
  } catch {
29218
29633
  }
29219
29634
  watchFile3(targetPath, { interval: 500 }, () => {
29220
29635
  try {
29221
- const content = readFileSync22(targetPath, "utf-8");
29636
+ const content = readFileSync23(targetPath, "utf-8");
29222
29637
  if (content.length > lastLength) {
29223
29638
  process.stdout.write(content.slice(lastLength));
29224
29639
  lastLength = content.length;
@@ -29262,11 +29677,11 @@ __export(gemini_exports, {
29262
29677
  geminiReorder: () => geminiReorder,
29263
29678
  geminiRotation: () => geminiRotation
29264
29679
  });
29265
- import { existsSync as existsSync33, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync23, chmodSync } from "fs";
29680
+ import { existsSync as existsSync34, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync24, chmodSync } from "fs";
29266
29681
  import { join as join32 } from "path";
29267
29682
  import { createInterface as createInterface8 } from "readline";
29268
29683
  function requireDb() {
29269
- if (!existsSync33(DB_PATH)) {
29684
+ if (!existsSync34(DB_PATH)) {
29270
29685
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
29271
29686
  process.exit(1);
29272
29687
  }
@@ -29292,8 +29707,8 @@ function resolveOAuthEmail(configHome) {
29292
29707
  if (!configHome) return null;
29293
29708
  try {
29294
29709
  const accountsPath = join32(configHome, ".gemini", "google_accounts.json");
29295
- if (!existsSync33(accountsPath)) return null;
29296
- const accounts = JSON.parse(readFileSync23(accountsPath, "utf-8"));
29710
+ if (!existsSync34(accountsPath)) return null;
29711
+ const accounts = JSON.parse(readFileSync24(accountsPath, "utf-8"));
29297
29712
  return accounts.active || null;
29298
29713
  } catch {
29299
29714
  return null;
@@ -29376,7 +29791,7 @@ async function geminiAddKey(globalOpts, opts) {
29376
29791
  async function geminiAddAccount(globalOpts, opts) {
29377
29792
  await requireWriteDb();
29378
29793
  const slotsDir = join32(CC_CLAW_HOME, "gemini-slots");
29379
- if (!existsSync33(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
29794
+ if (!existsSync34(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
29380
29795
  const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
29381
29796
  const tempId = Date.now();
29382
29797
  const slotDir = join32(slotsDir, `slot-${tempId}`);
@@ -29390,9 +29805,9 @@ async function geminiAddAccount(globalOpts, opts) {
29390
29805
  console.log(" Sign in with the Google account you want for this slot.");
29391
29806
  console.log(" After sign-in, type /quit to return here.");
29392
29807
  console.log("");
29393
- const { execSync: execSync6 } = await import("child_process");
29808
+ const { execSync: execSync7 } = await import("child_process");
29394
29809
  try {
29395
- execSync6(`GEMINI_CLI_HOME=${slotDir} gemini`, {
29810
+ execSync7(`GEMINI_CLI_HOME=${slotDir} gemini`, {
29396
29811
  stdio: "inherit",
29397
29812
  env: { ...process.env, GEMINI_CLI_HOME: slotDir, GEMINI_API_KEY: void 0, GOOGLE_API_KEY: void 0 },
29398
29813
  cwd: slotDir
@@ -29400,7 +29815,7 @@ async function geminiAddAccount(globalOpts, opts) {
29400
29815
  } catch {
29401
29816
  }
29402
29817
  const oauthPath = join32(slotDir, ".gemini", "oauth_creds.json");
29403
- if (!existsSync33(oauthPath)) {
29818
+ if (!existsSync34(oauthPath)) {
29404
29819
  console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
29405
29820
  console.log(" The slot directory is preserved at: " + slotDir);
29406
29821
  console.log(" Re-run: cc-claw gemini add-account\n");
@@ -29520,7 +29935,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
29520
29935
  return;
29521
29936
  }
29522
29937
  const settingsPath = join32(slot.configHome, ".gemini", "settings.json");
29523
- if (!existsSync33(settingsPath)) {
29938
+ if (!existsSync34(settingsPath)) {
29524
29939
  mkdirSync14(join32(slot.configHome, ".gemini"), { recursive: true });
29525
29940
  writeFileSync10(settingsPath, JSON.stringify({
29526
29941
  security: { auth: { selectedType: "oauth-personal" } }
@@ -29531,9 +29946,9 @@ async function geminiRelogin(globalOpts, idOrLabel) {
29531
29946
  console.log(" Sign in with the same Google account when prompted.");
29532
29947
  console.log(" After sign-in, type /quit to return here.");
29533
29948
  console.log("");
29534
- const { execSync: execSync6 } = await import("child_process");
29949
+ const { execSync: execSync7 } = await import("child_process");
29535
29950
  try {
29536
- execSync6(`gemini`, {
29951
+ execSync7(`gemini`, {
29537
29952
  stdio: "inherit",
29538
29953
  env: {
29539
29954
  ...process.env,
@@ -29546,7 +29961,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
29546
29961
  } catch {
29547
29962
  }
29548
29963
  const oauthPath = join32(slot.configHome, ".gemini", "oauth_creds.json");
29549
- if (!existsSync33(oauthPath)) {
29964
+ if (!existsSync34(oauthPath)) {
29550
29965
  console.log(error2("\n Re-login failed \u2014 no OAuth credentials found."));
29551
29966
  console.log(` Try again: cc-claw gemini re-login ${idOrLabel}
29552
29967
  `);
@@ -29556,7 +29971,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
29556
29971
  setGeminiSlotEnabled2(slotId, true);
29557
29972
  let accountEmail = slot.label;
29558
29973
  try {
29559
- const accounts = JSON.parse(readFileSync23(join32(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
29974
+ const accounts = JSON.parse(readFileSync24(join32(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
29560
29975
  if (accounts.active) accountEmail = accounts.active;
29561
29976
  } catch {
29562
29977
  }
@@ -29615,11 +30030,11 @@ __export(backend_cmd_factory_exports, {
29615
30030
  makeReorder: () => makeReorder,
29616
30031
  registerBackendSlotCommands: () => registerBackendSlotCommands
29617
30032
  });
29618
- import { existsSync as existsSync34, mkdirSync as mkdirSync15, readFileSync as readFileSync24 } from "fs";
30033
+ import { existsSync as existsSync35, mkdirSync as mkdirSync15, readFileSync as readFileSync25 } from "fs";
29619
30034
  import { join as join33 } from "path";
29620
30035
  import { createInterface as createInterface9 } from "readline";
29621
30036
  function requireDb2() {
29622
- if (!existsSync34(DB_PATH)) {
30037
+ if (!existsSync35(DB_PATH)) {
29623
30038
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
29624
30039
  process.exit(1);
29625
30040
  }
@@ -29709,7 +30124,7 @@ function makeAddAccount(backend2, displayName) {
29709
30124
  }
29710
30125
  await requireWriteDb2();
29711
30126
  const slotsDir = join33(CC_CLAW_HOME, config2.slotsSubdir);
29712
- if (!existsSync34(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
30127
+ if (!existsSync35(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
29713
30128
  const tempId = Date.now();
29714
30129
  const slotDir = join33(slotsDir, `slot-${tempId}`);
29715
30130
  mkdirSync15(slotDir, { recursive: true, mode: 448 });
@@ -29719,14 +30134,14 @@ function makeAddAccount(backend2, displayName) {
29719
30134
  console.log(` Sign in with the account you want for this slot.`);
29720
30135
  console.log(` After sign-in completes, return here.`);
29721
30136
  console.log("");
29722
- const { execSync: execSync6 } = await import("child_process");
30137
+ const { execSync: execSync7 } = await import("child_process");
29723
30138
  const loginEnv = {
29724
30139
  ...process.env,
29725
30140
  [config2.envKey]: config2.envValue(slotDir),
29726
30141
  ...config2.envOverrides
29727
30142
  };
29728
30143
  try {
29729
- execSync6(config2.loginCommand.join(" "), {
30144
+ execSync7(config2.loginCommand.join(" "), {
29730
30145
  stdio: "inherit",
29731
30146
  env: loginEnv,
29732
30147
  cwd: slotDir
@@ -29871,14 +30286,14 @@ function makeRelogin(backend2, displayName) {
29871
30286
  console.log(` Re-authenticating ${displayName} slot "${slot.label || `#${slot.id}`}"...`);
29872
30287
  console.log(` Sign in with the same account when the browser opens.`);
29873
30288
  console.log("");
29874
- const { execSync: execSync6 } = await import("child_process");
30289
+ const { execSync: execSync7 } = await import("child_process");
29875
30290
  const loginEnv = {
29876
30291
  ...process.env,
29877
30292
  [config2.envKey]: config2.envValue(slot.configHome),
29878
30293
  ...config2.envOverrides
29879
30294
  };
29880
30295
  try {
29881
- execSync6(config2.loginCommand.join(" "), {
30296
+ execSync7(config2.loginCommand.join(" "), {
29882
30297
  stdio: "inherit",
29883
30298
  env: loginEnv,
29884
30299
  cwd: slot.configHome
@@ -29967,17 +30382,17 @@ var init_backend_cmd_factory = __esm({
29967
30382
  verifyCredentials: (slotDir) => {
29968
30383
  const claudeJson = join33(slotDir, ".claude.json");
29969
30384
  const claudeJsonNested = join33(slotDir, ".claude", ".claude.json");
29970
- if (existsSync34(claudeJson)) {
30385
+ if (existsSync35(claudeJson)) {
29971
30386
  try {
29972
- const data = JSON.parse(readFileSync24(claudeJson, "utf-8"));
30387
+ const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
29973
30388
  return Boolean(data.oauthAccount);
29974
30389
  } catch {
29975
30390
  return false;
29976
30391
  }
29977
30392
  }
29978
- if (existsSync34(claudeJsonNested)) {
30393
+ if (existsSync35(claudeJsonNested)) {
29979
30394
  try {
29980
- const data = JSON.parse(readFileSync24(claudeJsonNested, "utf-8"));
30395
+ const data = JSON.parse(readFileSync25(claudeJsonNested, "utf-8"));
29981
30396
  return Boolean(data.oauthAccount);
29982
30397
  } catch {
29983
30398
  return false;
@@ -29987,8 +30402,8 @@ var init_backend_cmd_factory = __esm({
29987
30402
  },
29988
30403
  extractLabel: (slotDir) => {
29989
30404
  try {
29990
- const { execSync: execSync6 } = __require("child_process");
29991
- const out = execSync6("claude auth status", {
30405
+ const { execSync: execSync7 } = __require("child_process");
30406
+ const out = execSync7("claude auth status", {
29992
30407
  encoding: "utf-8",
29993
30408
  env: { ...process.env, HOME: slotDir, ANTHROPIC_API_KEY: void 0 },
29994
30409
  timeout: 1e4
@@ -29999,8 +30414,8 @@ var init_backend_cmd_factory = __esm({
29999
30414
  }
30000
30415
  try {
30001
30416
  const claudeJson = join33(slotDir, ".claude.json");
30002
- if (existsSync34(claudeJson)) {
30003
- const data = JSON.parse(readFileSync24(claudeJson, "utf-8"));
30417
+ if (existsSync35(claudeJson)) {
30418
+ const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
30004
30419
  if (data.oauthAccount?.emailAddress) return data.oauthAccount.emailAddress;
30005
30420
  }
30006
30421
  } catch {
@@ -30015,11 +30430,11 @@ var init_backend_cmd_factory = __esm({
30015
30430
  envValue: (slotDir) => slotDir,
30016
30431
  envOverrides: { OPENAI_API_KEY: void 0 },
30017
30432
  verifyCredentials: (slotDir) => {
30018
- return existsSync34(join33(slotDir, "auth.json"));
30433
+ return existsSync35(join33(slotDir, "auth.json"));
30019
30434
  },
30020
30435
  extractLabel: (slotDir) => {
30021
30436
  try {
30022
- const authData = JSON.parse(readFileSync24(join33(slotDir, "auth.json"), "utf-8"));
30437
+ const authData = JSON.parse(readFileSync25(join33(slotDir, "auth.json"), "utf-8"));
30023
30438
  if (authData.email) return authData.email;
30024
30439
  if (authData.account_name) return authData.account_name;
30025
30440
  if (authData.user?.email) return authData.user.email;
@@ -30043,9 +30458,9 @@ __export(ollama_exports3, {
30043
30458
  ollamaRemove: () => ollamaRemove,
30044
30459
  ollamaTest: () => ollamaTest
30045
30460
  });
30046
- import { existsSync as existsSync35 } from "fs";
30461
+ import { existsSync as existsSync36 } from "fs";
30047
30462
  function requireDb3() {
30048
- if (!existsSync35(DB_PATH)) {
30463
+ if (!existsSync36(DB_PATH)) {
30049
30464
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30050
30465
  process.exit(1);
30051
30466
  }
@@ -30304,12 +30719,12 @@ __export(backend_exports, {
30304
30719
  backendList: () => backendList,
30305
30720
  backendSet: () => backendSet
30306
30721
  });
30307
- import { existsSync as existsSync36 } from "fs";
30722
+ import { existsSync as existsSync37 } from "fs";
30308
30723
  async function backendList(globalOpts) {
30309
30724
  const { getAvailableAdapters: getAvailableAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
30310
30725
  const chatId = resolveChatId2(globalOpts);
30311
30726
  let activeBackend = null;
30312
- if (existsSync36(DB_PATH)) {
30727
+ if (existsSync37(DB_PATH)) {
30313
30728
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
30314
30729
  const readDb = openDatabaseReadOnly2();
30315
30730
  try {
@@ -30340,7 +30755,7 @@ async function backendList(globalOpts) {
30340
30755
  }
30341
30756
  async function backendGet(globalOpts) {
30342
30757
  const chatId = resolveChatId2(globalOpts);
30343
- if (!existsSync36(DB_PATH)) {
30758
+ if (!existsSync37(DB_PATH)) {
30344
30759
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30345
30760
  process.exit(1);
30346
30761
  }
@@ -30384,13 +30799,13 @@ __export(model_exports, {
30384
30799
  modelList: () => modelList,
30385
30800
  modelSet: () => modelSet
30386
30801
  });
30387
- import { existsSync as existsSync37 } from "fs";
30802
+ import { existsSync as existsSync38 } from "fs";
30388
30803
  async function modelList(globalOpts) {
30389
30804
  const chatId = resolveChatId2(globalOpts);
30390
30805
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
30391
- const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
30806
+ const { getAdapter: getAdapter5, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
30392
30807
  let backendId = "claude";
30393
- if (existsSync37(DB_PATH)) {
30808
+ if (existsSync38(DB_PATH)) {
30394
30809
  const readDb = openDatabaseReadOnly2();
30395
30810
  try {
30396
30811
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -30400,7 +30815,7 @@ async function modelList(globalOpts) {
30400
30815
  readDb.close();
30401
30816
  }
30402
30817
  try {
30403
- const adapter = getAdapter4(backendId);
30818
+ const adapter = getAdapter5(backendId);
30404
30819
  const models = Object.entries(adapter.availableModels).map(([id, info]) => ({
30405
30820
  id,
30406
30821
  label: info.label,
@@ -30423,7 +30838,7 @@ async function modelList(globalOpts) {
30423
30838
  }
30424
30839
  async function modelGet(globalOpts) {
30425
30840
  const chatId = resolveChatId2(globalOpts);
30426
- if (!existsSync37(DB_PATH)) {
30841
+ if (!existsSync38(DB_PATH)) {
30427
30842
  outputError("DB_NOT_FOUND", "Database not found.");
30428
30843
  process.exit(1);
30429
30844
  }
@@ -30467,9 +30882,9 @@ __export(memory_exports2, {
30467
30882
  memoryList: () => memoryList,
30468
30883
  memorySearch: () => memorySearch
30469
30884
  });
30470
- import { existsSync as existsSync38 } from "fs";
30885
+ import { existsSync as existsSync39 } from "fs";
30471
30886
  async function memoryList(globalOpts) {
30472
- if (!existsSync38(DB_PATH)) {
30887
+ if (!existsSync39(DB_PATH)) {
30473
30888
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30474
30889
  process.exit(1);
30475
30890
  }
@@ -30493,7 +30908,7 @@ async function memoryList(globalOpts) {
30493
30908
  });
30494
30909
  }
30495
30910
  async function memorySearch(globalOpts, query) {
30496
- if (!existsSync38(DB_PATH)) {
30911
+ if (!existsSync39(DB_PATH)) {
30497
30912
  outputError("DB_NOT_FOUND", "Database not found.");
30498
30913
  process.exit(1);
30499
30914
  }
@@ -30515,7 +30930,7 @@ async function memorySearch(globalOpts, query) {
30515
30930
  });
30516
30931
  }
30517
30932
  async function memoryHistory(globalOpts, opts) {
30518
- if (!existsSync38(DB_PATH)) {
30933
+ if (!existsSync39(DB_PATH)) {
30519
30934
  outputError("DB_NOT_FOUND", "Database not found.");
30520
30935
  process.exit(1);
30521
30936
  }
@@ -30563,7 +30978,7 @@ __export(cron_exports2, {
30563
30978
  cronList: () => cronList,
30564
30979
  cronRuns: () => cronRuns
30565
30980
  });
30566
- import { existsSync as existsSync39 } from "fs";
30981
+ import { existsSync as existsSync40 } from "fs";
30567
30982
  function parseFallbacks(raw) {
30568
30983
  return raw.slice(0, 3).map((f) => {
30569
30984
  const [backend2, ...rest] = f.split(":");
@@ -30584,7 +30999,7 @@ function parseAndValidateTimeout(raw) {
30584
30999
  return val;
30585
31000
  }
30586
31001
  async function cronList(globalOpts) {
30587
- if (!existsSync39(DB_PATH)) {
31002
+ if (!existsSync40(DB_PATH)) {
30588
31003
  outputError("DB_NOT_FOUND", "Database not found.");
30589
31004
  process.exit(1);
30590
31005
  }
@@ -30622,7 +31037,7 @@ async function cronList(globalOpts) {
30622
31037
  });
30623
31038
  }
30624
31039
  async function cronHealth(globalOpts) {
30625
- if (!existsSync39(DB_PATH)) {
31040
+ if (!existsSync40(DB_PATH)) {
30626
31041
  outputError("DB_NOT_FOUND", "Database not found.");
30627
31042
  process.exit(1);
30628
31043
  }
@@ -30783,7 +31198,7 @@ async function cronEdit(globalOpts, id, opts) {
30783
31198
  }
30784
31199
  }
30785
31200
  async function cronRuns(globalOpts, jobId, opts) {
30786
- if (!existsSync39(DB_PATH)) {
31201
+ if (!existsSync40(DB_PATH)) {
30787
31202
  outputError("DB_NOT_FOUND", "Database not found.");
30788
31203
  process.exit(1);
30789
31204
  }
@@ -30830,9 +31245,9 @@ __export(agents_exports, {
30830
31245
  runnersList: () => runnersList,
30831
31246
  tasksList: () => tasksList
30832
31247
  });
30833
- import { existsSync as existsSync40 } from "fs";
31248
+ import { existsSync as existsSync41 } from "fs";
30834
31249
  async function agentsList(globalOpts) {
30835
- if (!existsSync40(DB_PATH)) {
31250
+ if (!existsSync41(DB_PATH)) {
30836
31251
  outputError("DB_NOT_FOUND", "Database not found.");
30837
31252
  process.exit(1);
30838
31253
  }
@@ -30863,7 +31278,7 @@ async function agentsList(globalOpts) {
30863
31278
  });
30864
31279
  }
30865
31280
  async function tasksList(globalOpts) {
30866
- if (!existsSync40(DB_PATH)) {
31281
+ if (!existsSync41(DB_PATH)) {
30867
31282
  outputError("DB_NOT_FOUND", "Database not found.");
30868
31283
  process.exit(1);
30869
31284
  }
@@ -30991,10 +31406,10 @@ __export(db_exports, {
30991
31406
  dbPath: () => dbPath,
30992
31407
  dbStats: () => dbStats
30993
31408
  });
30994
- import { existsSync as existsSync41, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
31409
+ import { existsSync as existsSync42, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
30995
31410
  import { dirname as dirname7 } from "path";
30996
31411
  async function dbStats(globalOpts) {
30997
- if (!existsSync41(DB_PATH)) {
31412
+ if (!existsSync42(DB_PATH)) {
30998
31413
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
30999
31414
  process.exit(1);
31000
31415
  }
@@ -31002,7 +31417,7 @@ async function dbStats(globalOpts) {
31002
31417
  const readDb = openDatabaseReadOnly2();
31003
31418
  const mainSize = statSync11(DB_PATH).size;
31004
31419
  const walPath = DB_PATH + "-wal";
31005
- const walSize = existsSync41(walPath) ? statSync11(walPath).size : 0;
31420
+ const walSize = existsSync42(walPath) ? statSync11(walPath).size : 0;
31006
31421
  const tableNames = readDb.prepare(
31007
31422
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
31008
31423
  ).all();
@@ -31036,7 +31451,7 @@ async function dbPath(globalOpts) {
31036
31451
  output({ path: DB_PATH }, (d) => d.path);
31037
31452
  }
31038
31453
  async function dbBackup(globalOpts, destPath) {
31039
- if (!existsSync41(DB_PATH)) {
31454
+ if (!existsSync42(DB_PATH)) {
31040
31455
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
31041
31456
  process.exit(1);
31042
31457
  }
@@ -31045,7 +31460,7 @@ async function dbBackup(globalOpts, destPath) {
31045
31460
  mkdirSync16(dirname7(dest), { recursive: true });
31046
31461
  copyFileSync3(DB_PATH, dest);
31047
31462
  const walPath = DB_PATH + "-wal";
31048
- if (existsSync41(walPath)) copyFileSync3(walPath, dest + "-wal");
31463
+ if (existsSync42(walPath)) copyFileSync3(walPath, dest + "-wal");
31049
31464
  output({ path: dest, sizeBytes: statSync11(dest).size }, (d) => {
31050
31465
  const b = d;
31051
31466
  return `
@@ -31074,9 +31489,9 @@ __export(usage_exports, {
31074
31489
  usageCost: () => usageCost,
31075
31490
  usageTokens: () => usageTokens
31076
31491
  });
31077
- import { existsSync as existsSync42 } from "fs";
31492
+ import { existsSync as existsSync43 } from "fs";
31078
31493
  function ensureDb() {
31079
- if (!existsSync42(DB_PATH)) {
31494
+ if (!existsSync43(DB_PATH)) {
31080
31495
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
31081
31496
  process.exit(1);
31082
31497
  }
@@ -31266,9 +31681,9 @@ __export(config_exports2, {
31266
31681
  configList: () => configList,
31267
31682
  configSet: () => configSet
31268
31683
  });
31269
- import { existsSync as existsSync43, readFileSync as readFileSync25 } from "fs";
31684
+ import { existsSync as existsSync44, readFileSync as readFileSync26 } from "fs";
31270
31685
  async function configList(globalOpts) {
31271
- if (!existsSync43(DB_PATH)) {
31686
+ if (!existsSync44(DB_PATH)) {
31272
31687
  outputError("DB_NOT_FOUND", "Database not found.");
31273
31688
  process.exit(1);
31274
31689
  }
@@ -31302,7 +31717,7 @@ async function configGet(globalOpts, key) {
31302
31717
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
31303
31718
  process.exit(1);
31304
31719
  }
31305
- if (!existsSync43(DB_PATH)) {
31720
+ if (!existsSync44(DB_PATH)) {
31306
31721
  outputError("DB_NOT_FOUND", "Database not found.");
31307
31722
  process.exit(1);
31308
31723
  }
@@ -31348,11 +31763,11 @@ async function configSet(globalOpts, key, value) {
31348
31763
  }
31349
31764
  }
31350
31765
  async function configEnv(_globalOpts) {
31351
- if (!existsSync43(ENV_PATH)) {
31766
+ if (!existsSync44(ENV_PATH)) {
31352
31767
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
31353
31768
  process.exit(1);
31354
31769
  }
31355
- const content = readFileSync25(ENV_PATH, "utf-8");
31770
+ const content = readFileSync26(ENV_PATH, "utf-8");
31356
31771
  const entries = {};
31357
31772
  const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
31358
31773
  for (const line of content.split("\n")) {
@@ -31402,9 +31817,9 @@ __export(session_exports, {
31402
31817
  sessionGet: () => sessionGet,
31403
31818
  sessionNew: () => sessionNew
31404
31819
  });
31405
- import { existsSync as existsSync44 } from "fs";
31820
+ import { existsSync as existsSync45 } from "fs";
31406
31821
  async function sessionGet(globalOpts) {
31407
- if (!existsSync44(DB_PATH)) {
31822
+ if (!existsSync45(DB_PATH)) {
31408
31823
  outputError("DB_NOT_FOUND", "Database not found.");
31409
31824
  process.exit(1);
31410
31825
  }
@@ -31465,9 +31880,9 @@ __export(permissions_exports, {
31465
31880
  verboseGet: () => verboseGet,
31466
31881
  verboseSet: () => verboseSet
31467
31882
  });
31468
- import { existsSync as existsSync45 } from "fs";
31883
+ import { existsSync as existsSync46 } from "fs";
31469
31884
  function ensureDb2() {
31470
- if (!existsSync45(DB_PATH)) {
31885
+ if (!existsSync46(DB_PATH)) {
31471
31886
  outputError("DB_NOT_FOUND", "Database not found.");
31472
31887
  process.exit(1);
31473
31888
  }
@@ -31614,9 +32029,9 @@ __export(cwd_exports, {
31614
32029
  cwdGet: () => cwdGet,
31615
32030
  cwdSet: () => cwdSet
31616
32031
  });
31617
- import { existsSync as existsSync46 } from "fs";
32032
+ import { existsSync as existsSync47 } from "fs";
31618
32033
  async function cwdGet(globalOpts) {
31619
- if (!existsSync46(DB_PATH)) {
32034
+ if (!existsSync47(DB_PATH)) {
31620
32035
  outputError("DB_NOT_FOUND", "Database not found.");
31621
32036
  process.exit(1);
31622
32037
  }
@@ -31678,9 +32093,9 @@ __export(voice_exports, {
31678
32093
  voiceGet: () => voiceGet,
31679
32094
  voiceSet: () => voiceSet
31680
32095
  });
31681
- import { existsSync as existsSync47 } from "fs";
32096
+ import { existsSync as existsSync48 } from "fs";
31682
32097
  async function voiceGet(globalOpts) {
31683
- if (!existsSync47(DB_PATH)) {
32098
+ if (!existsSync48(DB_PATH)) {
31684
32099
  outputError("DB_NOT_FOUND", "Database not found.");
31685
32100
  process.exit(1);
31686
32101
  }
@@ -31729,9 +32144,9 @@ __export(heartbeat_exports, {
31729
32144
  heartbeatGet: () => heartbeatGet,
31730
32145
  heartbeatSet: () => heartbeatSet
31731
32146
  });
31732
- import { existsSync as existsSync48 } from "fs";
32147
+ import { existsSync as existsSync49 } from "fs";
31733
32148
  async function heartbeatGet(globalOpts) {
31734
- if (!existsSync48(DB_PATH)) {
32149
+ if (!existsSync49(DB_PATH)) {
31735
32150
  outputError("DB_NOT_FOUND", "Database not found.");
31736
32151
  process.exit(1);
31737
32152
  }
@@ -31746,8 +32161,25 @@ async function heartbeatGet(globalOpts) {
31746
32161
  intervalMinutes: row.interval_ms / 6e4,
31747
32162
  activeStart: row.active_start,
31748
32163
  activeEnd: row.active_end,
31749
- lastBeatAt: row.last_beat_at
31750
- } : { enabled: false, intervalMs: 18e5, intervalMinutes: 30, activeStart: "08:00", activeEnd: "22:00", lastBeatAt: null };
32164
+ lastBeatAt: row.last_beat_at,
32165
+ backend: row.backend ?? null,
32166
+ model: row.model ?? null,
32167
+ fallbacks: row.fallbacks ?? null,
32168
+ thinking: row.thinking ?? null,
32169
+ target: row.target ?? null
32170
+ } : {
32171
+ enabled: false,
32172
+ intervalMs: 18e5,
32173
+ intervalMinutes: 30,
32174
+ activeStart: "08:00",
32175
+ activeEnd: "22:00",
32176
+ lastBeatAt: null,
32177
+ backend: null,
32178
+ model: null,
32179
+ fallbacks: null,
32180
+ thinking: null,
32181
+ target: null
32182
+ };
31751
32183
  output(data, (d) => {
31752
32184
  const h = d;
31753
32185
  const lines = [
@@ -31757,9 +32189,30 @@ async function heartbeatGet(globalOpts) {
31757
32189
  kvLine("Status", h.enabled ? success("on") : muted("off")),
31758
32190
  kvLine("Interval", `${h.intervalMinutes}m`),
31759
32191
  kvLine("Active hours", `${h.activeStart} - ${h.activeEnd}`),
31760
- kvLine("Last beat", h.lastBeatAt ?? muted("never")),
31761
- ""
32192
+ kvLine("Backend", h.backend ?? muted("default")),
32193
+ kvLine("Model", h.model ?? muted("default")),
32194
+ kvLine("Thinking", h.thinking ?? muted("off"))
31762
32195
  ];
32196
+ if (h.fallbacks) {
32197
+ try {
32198
+ const parsed = JSON.parse(h.fallbacks);
32199
+ const chain = parsed.map((f) => `${f.backend}${f.model ? `:${f.model}` : ""}`).join(" \u2192 ");
32200
+ lines.push(kvLine("Fallbacks", chain));
32201
+ } catch {
32202
+ lines.push(kvLine("Fallbacks", h.fallbacks));
32203
+ }
32204
+ } else {
32205
+ lines.push(kvLine("Fallbacks", muted("none")));
32206
+ }
32207
+ if (h.target) {
32208
+ lines.push(kvLine("Target", h.target));
32209
+ }
32210
+ lines.push(kvLine("Last beat", h.lastBeatAt ?? muted("never")));
32211
+ if (!h.enabled) {
32212
+ lines.push("");
32213
+ lines.push(` ${warning("\u26A0 Heartbeat is disabled \u2014 you won't receive proactive alerts.")}`);
32214
+ }
32215
+ lines.push("");
31763
32216
  return lines.join("\n");
31764
32217
  });
31765
32218
  }
@@ -31781,7 +32234,7 @@ async function heartbeatSet(globalOpts, subcommand, value) {
31781
32234
  body.enabled = false;
31782
32235
  successMsg = "Heartbeat disabled.";
31783
32236
  break;
31784
- case "interval":
32237
+ case "interval": {
31785
32238
  if (!value) {
31786
32239
  outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set interval <value>");
31787
32240
  process.exit(1);
@@ -31797,7 +32250,8 @@ async function heartbeatSet(globalOpts, subcommand, value) {
31797
32250
  body.intervalMs = ms;
31798
32251
  successMsg = `Heartbeat interval: ${ms / 6e4}m`;
31799
32252
  break;
31800
- case "hours":
32253
+ }
32254
+ case "hours": {
31801
32255
  if (!value) {
31802
32256
  outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set hours <start>-<end>");
31803
32257
  process.exit(1);
@@ -31811,8 +32265,71 @@ async function heartbeatSet(globalOpts, subcommand, value) {
31811
32265
  body.activeEnd = `${hourMatch[2].padStart(2, "0")}:00`;
31812
32266
  successMsg = `Active hours: ${body.activeStart} - ${body.activeEnd}`;
31813
32267
  break;
32268
+ }
32269
+ case "backend": {
32270
+ if (!value) {
32271
+ outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set backend <name|default>");
32272
+ process.exit(1);
32273
+ }
32274
+ if (value === "default") {
32275
+ body.backend = null;
32276
+ body.model = null;
32277
+ successMsg = "Heartbeat backend: default (chat-level)";
32278
+ } else {
32279
+ body.backend = value;
32280
+ body.model = null;
32281
+ successMsg = `Heartbeat backend: ${value}`;
32282
+ }
32283
+ break;
32284
+ }
32285
+ case "model": {
32286
+ if (!value) {
32287
+ outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set model <name|default>");
32288
+ process.exit(1);
32289
+ }
32290
+ if (value === "default") {
32291
+ body.model = null;
32292
+ successMsg = "Heartbeat model: default";
32293
+ } else {
32294
+ body.model = value;
32295
+ successMsg = `Heartbeat model: ${value}`;
32296
+ }
32297
+ break;
32298
+ }
32299
+ case "thinking": {
32300
+ if (!value) {
32301
+ outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set thinking <off|low|medium|high>");
32302
+ process.exit(1);
32303
+ }
32304
+ const valid = ["off", "low", "medium", "high"];
32305
+ if (!valid.includes(value)) {
32306
+ outputError("INVALID_THINKING", `Valid values: ${valid.join(", ")}`);
32307
+ process.exit(1);
32308
+ }
32309
+ body.thinking = value === "off" ? null : value;
32310
+ successMsg = `Heartbeat thinking: ${value}`;
32311
+ break;
32312
+ }
32313
+ case "fallbacks": {
32314
+ if (!value) {
32315
+ outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set fallbacks <backend1,backend2|clear>");
32316
+ process.exit(1);
32317
+ }
32318
+ if (value === "clear" || value === "none") {
32319
+ body.fallbacks = null;
32320
+ successMsg = "Heartbeat fallbacks: cleared";
32321
+ } else {
32322
+ const chain = value.split(",").map((s) => {
32323
+ const [backend2, model2] = s.trim().split(":");
32324
+ return model2 ? { backend: backend2, model: model2 } : { backend: backend2 };
32325
+ });
32326
+ body.fallbacks = JSON.stringify(chain);
32327
+ successMsg = `Heartbeat fallbacks: ${chain.map((f) => f.backend).join(" \u2192 ")}`;
32328
+ }
32329
+ break;
32330
+ }
31814
32331
  default:
31815
- outputError("UNKNOWN_SUBCOMMAND", `Unknown: "${subcommand}". Use: on, off, interval, hours`);
32332
+ outputError("UNKNOWN_SUBCOMMAND", `Unknown: "${subcommand}". Use: on, off, interval, hours, backend, model, thinking, fallbacks`);
31816
32333
  process.exit(1);
31817
32334
  }
31818
32335
  const res = await apiPost2("/api/heartbeat/set", body);
@@ -31840,9 +32357,9 @@ __export(summarizer_exports, {
31840
32357
  summarizerGet: () => summarizerGet,
31841
32358
  summarizerSet: () => summarizerSet
31842
32359
  });
31843
- import { existsSync as existsSync49 } from "fs";
32360
+ import { existsSync as existsSync50 } from "fs";
31844
32361
  async function summarizerGet(globalOpts) {
31845
- if (!existsSync49(DB_PATH)) {
32362
+ if (!existsSync50(DB_PATH)) {
31846
32363
  outputError("DB_NOT_FOUND", "Database not found.");
31847
32364
  process.exit(1);
31848
32365
  }
@@ -31886,9 +32403,9 @@ __export(thinking_exports, {
31886
32403
  thinkingGet: () => thinkingGet,
31887
32404
  thinkingSet: () => thinkingSet
31888
32405
  });
31889
- import { existsSync as existsSync50 } from "fs";
32406
+ import { existsSync as existsSync51 } from "fs";
31890
32407
  async function thinkingGet(globalOpts) {
31891
- if (!existsSync50(DB_PATH)) {
32408
+ if (!existsSync51(DB_PATH)) {
31892
32409
  outputError("DB_NOT_FOUND", "Database not found.");
31893
32410
  process.exit(1);
31894
32411
  }
@@ -31932,9 +32449,9 @@ __export(chats_exports, {
31932
32449
  chatsList: () => chatsList,
31933
32450
  chatsRemoveAlias: () => chatsRemoveAlias
31934
32451
  });
31935
- import { existsSync as existsSync51 } from "fs";
32452
+ import { existsSync as existsSync52 } from "fs";
31936
32453
  async function chatsList(_globalOpts) {
31937
- if (!existsSync51(DB_PATH)) {
32454
+ if (!existsSync52(DB_PATH)) {
31938
32455
  outputError("DB_NOT_FOUND", "Database not found.");
31939
32456
  process.exit(1);
31940
32457
  }
@@ -32062,9 +32579,9 @@ var mcps_exports2 = {};
32062
32579
  __export(mcps_exports2, {
32063
32580
  mcpsList: () => mcpsList
32064
32581
  });
32065
- import { existsSync as existsSync52 } from "fs";
32582
+ import { existsSync as existsSync53 } from "fs";
32066
32583
  async function mcpsList(_globalOpts) {
32067
- if (!existsSync52(DB_PATH)) {
32584
+ if (!existsSync53(DB_PATH)) {
32068
32585
  outputError("DB_NOT_FOUND", "Database not found.");
32069
32586
  process.exit(1);
32070
32587
  }
@@ -32101,11 +32618,11 @@ __export(chat_exports2, {
32101
32618
  chatSend: () => chatSend
32102
32619
  });
32103
32620
  import { request as httpRequest2 } from "http";
32104
- import { readFileSync as readFileSync26, existsSync as existsSync53 } from "fs";
32621
+ import { readFileSync as readFileSync27, existsSync as existsSync54 } from "fs";
32105
32622
  function getToken2() {
32106
32623
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
32107
32624
  try {
32108
- if (existsSync53(TOKEN_PATH2)) return readFileSync26(TOKEN_PATH2, "utf-8").trim();
32625
+ if (existsSync54(TOKEN_PATH2)) return readFileSync27(TOKEN_PATH2, "utf-8").trim();
32109
32626
  } catch {
32110
32627
  }
32111
32628
  return null;
@@ -32785,9 +33302,9 @@ __export(evolve_exports2, {
32785
33302
  evolveStatus: () => evolveStatus,
32786
33303
  evolveUndo: () => evolveUndo
32787
33304
  });
32788
- import { existsSync as existsSync54 } from "fs";
33305
+ import { existsSync as existsSync55 } from "fs";
32789
33306
  function ensureDb3() {
32790
- if (!existsSync54(DB_PATH)) {
33307
+ if (!existsSync55(DB_PATH)) {
32791
33308
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32792
33309
  process.exit(1);
32793
33310
  }
@@ -33261,8 +33778,8 @@ var init_optimize2 = __esm({
33261
33778
 
33262
33779
  // src/setup.ts
33263
33780
  var setup_exports = {};
33264
- import { existsSync as existsSync55, writeFileSync as writeFileSync12, readFileSync as readFileSync27, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
33265
- import { execFileSync as execFileSync5 } from "child_process";
33781
+ import { existsSync as existsSync56, writeFileSync as writeFileSync12, readFileSync as readFileSync28, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
33782
+ import { execFileSync as execFileSync6 } from "child_process";
33266
33783
  import { createInterface as createInterface11 } from "readline";
33267
33784
  import { join as join35 } from "path";
33268
33785
  function divider2() {
@@ -33326,7 +33843,7 @@ async function setup() {
33326
33843
  let foundAnyBackend = false;
33327
33844
  for (const bk of backends) {
33328
33845
  try {
33329
- const path = execFileSync5("which", [bk.cmd], { encoding: "utf-8" }).trim();
33846
+ const path = execFileSync6("which", [bk.cmd], { encoding: "utf-8" }).trim();
33330
33847
  console.log(green(` ${bk.name} CLI found: ${path}`));
33331
33848
  foundAnyBackend = true;
33332
33849
  } catch {
@@ -33339,21 +33856,21 @@ async function setup() {
33339
33856
  }
33340
33857
  console.log("");
33341
33858
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
33342
- if (!existsSync55(dir)) mkdirSync18(dir, { recursive: true });
33859
+ if (!existsSync56(dir)) mkdirSync18(dir, { recursive: true });
33343
33860
  }
33344
33861
  const env = {};
33345
- const envSource = existsSync55(ENV_PATH) ? ENV_PATH : existsSync55(".env") ? ".env" : null;
33862
+ const envSource = existsSync56(ENV_PATH) ? ENV_PATH : existsSync56(".env") ? ".env" : null;
33346
33863
  if (envSource) {
33347
33864
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
33348
33865
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
33349
- const existing = readFileSync27(envSource, "utf-8");
33866
+ const existing = readFileSync28(envSource, "utf-8");
33350
33867
  for (const line of existing.split("\n")) {
33351
33868
  const match = line.match(/^([^#=]+)=(.*)$/);
33352
33869
  if (match) env[match[1].trim()] = match[2].trim();
33353
33870
  }
33354
33871
  }
33355
33872
  const cwdDb = join35(process.cwd(), "cc-claw.db");
33356
- if (existsSync55(cwdDb) && !existsSync55(DB_PATH)) {
33873
+ if (existsSync56(cwdDb) && !existsSync56(DB_PATH)) {
33357
33874
  const { size } = statSync12(cwdDb);
33358
33875
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
33359
33876
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
@@ -34073,7 +34590,7 @@ heartbeat.command("get").description("Show heartbeat status and config").action(
34073
34590
  const { heartbeatGet: heartbeatGet2 } = await Promise.resolve().then(() => (init_heartbeat3(), heartbeat_exports));
34074
34591
  await heartbeatGet2(program.opts());
34075
34592
  });
34076
- heartbeat.command("set <subcommand> [value]").description("Configure heartbeat (on/off/interval <val>/hours <range>)").action(async (subcommand, value) => {
34593
+ heartbeat.command("set <subcommand> [value]").description("Configure heartbeat (on/off/interval/hours/backend/model/thinking/fallbacks)").action(async (subcommand, value) => {
34077
34594
  const { heartbeatSet: heartbeatSet2 } = await Promise.resolve().then(() => (init_heartbeat3(), heartbeat_exports));
34078
34595
  await heartbeatSet2(program.opts(), subcommand, value);
34079
34596
  });
@@ -34246,8 +34763,8 @@ async function run(argv = process.argv) {
34246
34763
  if (argv.includes("--version") || argv.includes("-V")) {
34247
34764
  console.log(VERSION);
34248
34765
  try {
34249
- const { execFileSync: execFileSync6 } = await import("child_process");
34250
- const latest = execFileSync6("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
34766
+ const { execFileSync: execFileSync7 } = await import("child_process");
34767
+ const latest = execFileSync7("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
34251
34768
  if (latest && latest !== VERSION) {
34252
34769
  console.log(`
34253
34770
  Update available: v${latest} (current: v${VERSION})`);