cc-claw 0.4.5 → 0.4.7

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 +253 -160
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -48,7 +48,7 @@ var VERSION;
48
48
  var init_version = __esm({
49
49
  "src/version.ts"() {
50
50
  "use strict";
51
- VERSION = true ? "0.4.5" : (() => {
51
+ VERSION = true ? "0.4.7" : (() => {
52
52
  try {
53
53
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
54
54
  } catch {
@@ -358,10 +358,10 @@ function claimTask(db3, taskId, agentId) {
358
358
  ).get(...blockedBy);
359
359
  if (incomplete.count > 0) return false;
360
360
  }
361
- db3.prepare(
361
+ const result = db3.prepare(
362
362
  "UPDATE agent_tasks SET assignee = ?, status = 'in_progress' WHERE id = ? AND status = 'pending'"
363
363
  ).run(agentId, taskId);
364
- return true;
364
+ return result.changes > 0;
365
365
  }
366
366
  function sendInboxMessage(db3, opts) {
367
367
  const result = db3.prepare(`
@@ -2490,6 +2490,7 @@ var init_store4 = __esm({
2490
2490
  });
2491
2491
 
2492
2492
  // src/env.ts
2493
+ import { homedir as homedir2 } from "os";
2493
2494
  function stripProxyVars(env) {
2494
2495
  for (const key of PROXY_KEYS) delete env[key];
2495
2496
  }
@@ -2498,7 +2499,7 @@ function buildBaseEnv(extraOverrides) {
2498
2499
  for (const [k, v] of Object.entries(process.env)) {
2499
2500
  if (v !== void 0) env[k] = v;
2500
2501
  }
2501
- if (!env.HOME) env.HOME = `/Users/${process.env.USER ?? "unknown"}`;
2502
+ if (!env.HOME) env.HOME = homedir2();
2502
2503
  stripProxyVars(env);
2503
2504
  if (extraOverrides) Object.assign(env, extraOverrides);
2504
2505
  return env;
@@ -3208,8 +3209,9 @@ async function injectMemoryContext(userMessage) {
3208
3209
  seen.add(mem.id);
3209
3210
  combinedMemories.push(mem);
3210
3211
  }
3212
+ if (combinedMemories.length >= FINAL_TOP_K_MEMORIES) break;
3211
3213
  }
3212
- combinedSessions = ftsSessions;
3214
+ combinedSessions = ftsSessions.slice(0, FINAL_TOP_K_SESSIONS);
3213
3215
  }
3214
3216
  if (combinedMemories.length === 0 && combinedSessions.length === 0) return null;
3215
3217
  const lines = [];
@@ -4000,8 +4002,10 @@ function spawnAgentProcess(runner, opts, callbacks) {
4000
4002
  const child = spawn2(runner.getExecutablePath(), args, {
4001
4003
  env: buildSpawnEnv(runner, opts.isSubAgent),
4002
4004
  cwd: opts.cwd,
4003
- stdio: ["ignore", "pipe", "pipe"]
4005
+ stdio: ["ignore", "pipe", "pipe"],
4006
+ detached: true
4004
4007
  });
4008
+ child.unref();
4005
4009
  let resultText = "";
4006
4010
  let usage2;
4007
4011
  if (child.stdout) {
@@ -4024,7 +4028,14 @@ function spawnAgentProcess(runner, opts, callbacks) {
4024
4028
  if (event.usage) usage2 = event.usage;
4025
4029
  callbacks.onResult?.(resultText, usage2);
4026
4030
  if (runner.shouldKillOnResult()) {
4027
- child.kill("SIGTERM");
4031
+ try {
4032
+ if (child.pid) process.kill(-child.pid, "SIGTERM");
4033
+ } catch {
4034
+ try {
4035
+ child.kill("SIGTERM");
4036
+ } catch {
4037
+ }
4038
+ }
4028
4039
  }
4029
4040
  }
4030
4041
  }
@@ -4104,7 +4115,7 @@ var init_cost = __esm({
4104
4115
  // src/mcps/propagate.ts
4105
4116
  import { execFile } from "child_process";
4106
4117
  import { promisify } from "util";
4107
- import { homedir as homedir2 } from "os";
4118
+ import { homedir as homedir3 } from "os";
4108
4119
  async function discoverExistingMcps(runner) {
4109
4120
  try {
4110
4121
  const listCmd = runner.getMcpListCommand();
@@ -4113,7 +4124,7 @@ async function discoverExistingMcps(runner) {
4113
4124
  const result = await execFileAsync(exe, args, {
4114
4125
  encoding: "utf-8",
4115
4126
  env: runner.getEnv(),
4116
- cwd: homedir2(),
4127
+ cwd: homedir3(),
4117
4128
  timeout: 3e4
4118
4129
  });
4119
4130
  const stdout = typeof result === "string" ? result : Array.isArray(result) ? result[0] : result?.stdout ?? null;
@@ -4628,6 +4639,26 @@ async function startAgent(agentId, chatId, opts) {
4628
4639
  clearTimeout(timeoutTimers.get(agentId));
4629
4640
  timeoutTimers.delete(agentId);
4630
4641
  activeProcesses.delete(agentId);
4642
+ const crashedAgent = getAgent(db3, agentId);
4643
+ if (crashedAgent) {
4644
+ const mcpsCrashed = crashedAgent.mcpsAdded ? JSON.parse(crashedAgent.mcpsAdded) : [];
4645
+ if (mcpsCrashed.length > 0) {
4646
+ const runner2 = getRunner(crashedAgent.runnerId);
4647
+ if (runner2) {
4648
+ const cleanupFn = () => cleanupMcps(runner2, mcpsCrashed, db3, `agent:${agentId}`);
4649
+ if (runner2.capabilities.mcpInjection === "add-remove") {
4650
+ withRunnerLock(runner2.id, cleanupFn).catch((err) => {
4651
+ warn(`[orchestrator] MCP cleanup failed for crashed agent ${agentId.slice(0, 8)}:`, err);
4652
+ });
4653
+ } else {
4654
+ cleanupFn().catch((err) => {
4655
+ warn(`[orchestrator] MCP cleanup failed for crashed agent ${agentId.slice(0, 8)}:`, err);
4656
+ });
4657
+ }
4658
+ }
4659
+ }
4660
+ deleteMcpConfigFile(`cc-claw-${agentId.slice(0, 8)}`);
4661
+ }
4631
4662
  sendInboxMessage(db3, {
4632
4663
  orchestrationId: agent.orchestrationId,
4633
4664
  toAgentId: "main",
@@ -4789,9 +4820,23 @@ function cancelAgent(agentId, reason = "user_cancelled") {
4789
4820
  if (!agent) return false;
4790
4821
  const proc = activeProcesses.get(agentId);
4791
4822
  if (proc) {
4792
- proc.kill("SIGTERM");
4823
+ try {
4824
+ if (proc.pid) process.kill(-proc.pid, "SIGTERM");
4825
+ } catch {
4826
+ try {
4827
+ proc.kill("SIGTERM");
4828
+ } catch {
4829
+ }
4830
+ }
4793
4831
  setTimeout(() => {
4794
- if (!proc.killed) proc.kill("SIGKILL");
4832
+ try {
4833
+ if (proc.pid) process.kill(-proc.pid, "SIGKILL");
4834
+ } catch {
4835
+ try {
4836
+ proc.kill("SIGKILL");
4837
+ } catch {
4838
+ }
4839
+ }
4795
4840
  }, 2e3);
4796
4841
  }
4797
4842
  updateAgentStatus(db3, agentId, "cancelled");
@@ -4836,7 +4881,14 @@ function cancelAllAgents(chatId, reason = "user_cancelled") {
4836
4881
  }
4837
4882
  function shutdownOrchestrator() {
4838
4883
  for (const [agentId, proc] of activeProcesses) {
4839
- proc.kill("SIGTERM");
4884
+ try {
4885
+ if (proc.pid) process.kill(-proc.pid, "SIGTERM");
4886
+ } catch {
4887
+ try {
4888
+ proc.kill("SIGTERM");
4889
+ } catch {
4890
+ }
4891
+ }
4840
4892
  clearTimeout(timeoutTimers.get(agentId));
4841
4893
  }
4842
4894
  activeProcesses.clear();
@@ -5532,7 +5584,7 @@ function startDashboard() {
5532
5584
  return jsonResponse(res, { error: "message and chatId required" }, 400);
5533
5585
  }
5534
5586
  const { askAgent: askAgent2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
5535
- const { getMode: getMode2, getCwd: getCwd2, getModel: getModel3, addUsage: addUsage2, getBackend: getBackend2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
5587
+ const { getMode: getMode2, getCwd: getCwd2, getModel: getModel2, addUsage: addUsage2, getBackend: getBackend2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
5536
5588
  const { getAdapterForChat: getAdapterForChat2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
5537
5589
  const chatId = body.chatId;
5538
5590
  const PERM_LEVEL = { plan: 0, safe: 1, yolo: 2 };
@@ -5540,7 +5592,7 @@ function startDashboard() {
5540
5592
  const requestedMode = body.mode ?? storedMode;
5541
5593
  const mode = (PERM_LEVEL[requestedMode] ?? 2) <= (PERM_LEVEL[storedMode] ?? 2) ? requestedMode : storedMode;
5542
5594
  const cwd = body.cwd ?? getCwd2(chatId);
5543
- const model2 = body.model ?? getModel3(chatId) ?? (() => {
5595
+ const model2 = body.model ?? getModel2(chatId) ?? (() => {
5544
5596
  try {
5545
5597
  return getAdapterForChat2(chatId).defaultModel;
5546
5598
  } catch {
@@ -5696,8 +5748,8 @@ data: ${JSON.stringify(data)}
5696
5748
  if (url.pathname === "/api/heartbeat/set" && req.method === "POST") {
5697
5749
  try {
5698
5750
  const body = JSON.parse(await readBody(req));
5699
- const { setHeartbeatConfig: setHeartbeatConfig3 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
5700
- setHeartbeatConfig3(body.chatId, body);
5751
+ const { setHeartbeatConfig: setHeartbeatConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
5752
+ setHeartbeatConfig2(body.chatId, body);
5701
5753
  return jsonResponse(res, { success: true });
5702
5754
  } catch (err) {
5703
5755
  return jsonResponse(res, { error: errorMessage(err) }, 400);
@@ -5998,14 +6050,21 @@ var init_server = __esm({
5998
6050
  var agent_exports = {};
5999
6051
  __export(agent_exports, {
6000
6052
  askAgent: () => askAgent,
6001
- isAgentActive: () => isAgentActive,
6002
6053
  isChatBusy: () => isChatBusy,
6003
6054
  stopAgent: () => stopAgent
6004
6055
  });
6005
6056
  import { spawn as spawn4 } from "child_process";
6006
6057
  import { createInterface as createInterface3 } from "readline";
6007
- import { dirname as dirname2 } from "path";
6008
- import { fileURLToPath as fileURLToPath2 } from "url";
6058
+ function killProcessGroup(proc, signal = "SIGTERM") {
6059
+ try {
6060
+ if (proc.pid) process.kill(-proc.pid, signal);
6061
+ } catch {
6062
+ try {
6063
+ proc.kill(signal);
6064
+ } catch {
6065
+ }
6066
+ }
6067
+ }
6009
6068
  function withChatLock(chatId, fn) {
6010
6069
  const prev = chatLocks.get(chatId) ?? Promise.resolve();
6011
6070
  const isBlocked = activeChats.has(chatId);
@@ -6026,14 +6085,13 @@ function stopAgent(chatId) {
6026
6085
  if (!state) return false;
6027
6086
  state.cancelled = true;
6028
6087
  if (state.process) {
6029
- state.process.kill("SIGTERM");
6030
- state.killTimer = setTimeout(() => state.process?.kill("SIGKILL"), 2e3);
6088
+ killProcessGroup(state.process, "SIGTERM");
6089
+ state.killTimer = setTimeout(() => {
6090
+ if (state.process) killProcessGroup(state.process, "SIGKILL");
6091
+ }, 2e3);
6031
6092
  }
6032
6093
  return true;
6033
6094
  }
6034
- function isAgentActive(chatId) {
6035
- return activeChats.has(chatId);
6036
- }
6037
6095
  function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs) {
6038
6096
  const effectiveTimeout = timeoutMs ?? SPAWN_TIMEOUT_MS;
6039
6097
  return new Promise((resolve, reject) => {
@@ -6044,21 +6102,18 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
6044
6102
  const proc = spawn4(config2.executable, finalArgs, {
6045
6103
  env,
6046
6104
  stdio: ["ignore", "pipe", "pipe"],
6105
+ detached: true,
6047
6106
  ...config2.cwd ? { cwd: config2.cwd } : {}
6048
6107
  });
6108
+ proc.unref();
6049
6109
  cancelState.process = proc;
6050
6110
  let timedOut = false;
6051
6111
  let sigkillTimer;
6052
6112
  const spawnTimeout = setTimeout(() => {
6053
6113
  timedOut = true;
6054
6114
  warn(`[agent] Spawn timeout after ${effectiveTimeout / 1e3}s for ${adapter.id} \u2014 killing process`);
6055
- proc.kill("SIGTERM");
6056
- sigkillTimer = setTimeout(() => {
6057
- try {
6058
- proc.kill("SIGKILL");
6059
- } catch {
6060
- }
6061
- }, 3e3);
6115
+ killProcessGroup(proc, "SIGTERM");
6116
+ sigkillTimer = setTimeout(() => killProcessGroup(proc, "SIGKILL"), 3e3);
6062
6117
  }, effectiveTimeout);
6063
6118
  let resultText = "";
6064
6119
  let accumulatedText = "";
@@ -6146,7 +6201,7 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
6146
6201
  rl2.close();
6147
6202
  } catch {
6148
6203
  }
6149
- proc.kill("SIGTERM");
6204
+ killProcessGroup(proc, "SIGTERM");
6150
6205
  }
6151
6206
  break;
6152
6207
  }
@@ -6288,7 +6343,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
6288
6343
  if (!flag) return args;
6289
6344
  return [...args, ...flag, mcpConfigPath];
6290
6345
  }
6291
- var __filename2, __dirname2, activeChats, chatLocks, SPAWN_TIMEOUT_MS, MCP_CONFIG_FLAG;
6346
+ var activeChats, chatLocks, SPAWN_TIMEOUT_MS, MCP_CONFIG_FLAG;
6292
6347
  var init_agent = __esm({
6293
6348
  "src/agent.ts"() {
6294
6349
  "use strict";
@@ -6301,8 +6356,6 @@ var init_agent = __esm({
6301
6356
  init_summarize();
6302
6357
  init_server();
6303
6358
  init_mcp_config();
6304
- __filename2 = fileURLToPath2(import.meta.url);
6305
- __dirname2 = dirname2(__filename2);
6306
6359
  activeChats = /* @__PURE__ */ new Map();
6307
6360
  chatLocks = /* @__PURE__ */ new Map();
6308
6361
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
@@ -6384,7 +6437,7 @@ ${responseText.slice(0, 500)}`);
6384
6437
  if (!cleanText) return true;
6385
6438
  if (channelName === "telegram") {
6386
6439
  const parsed = parseTelegramTarget(targetChatId);
6387
- await channel.sendText(parsed.chatId, cleanText);
6440
+ await channel.sendText(parsed.chatId, cleanText, void 0, parsed.threadId);
6388
6441
  } else {
6389
6442
  await channel.sendText(targetChatId, cleanText);
6390
6443
  }
@@ -6410,7 +6463,8 @@ async function deliverWebhook(job, responseText) {
6410
6463
  description: job.description,
6411
6464
  text: responseText,
6412
6465
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6413
- })
6466
+ }),
6467
+ signal: AbortSignal.timeout(3e4)
6414
6468
  });
6415
6469
  if (!resp.ok) {
6416
6470
  error(`[delivery] Webhook POST to ${url} failed: ${resp.status} ${resp.statusText}`);
@@ -6815,6 +6869,9 @@ async function executeJob(job) {
6815
6869
  }
6816
6870
  } finally {
6817
6871
  runningJobs.delete(job.id);
6872
+ if (job.sessionType === "isolated" && job.thinking && job.thinking !== "auto") {
6873
+ clearThinkingLevel(`cron:${job.id}:${runId}`);
6874
+ }
6818
6875
  }
6819
6876
  }
6820
6877
  async function runWithRetry(job, model2, runId, t0) {
@@ -6911,7 +6968,7 @@ var init_cron = __esm({
6911
6968
  });
6912
6969
 
6913
6970
  // src/agents/runners/wrap-backend.ts
6914
- import { join as join8 } from "path";
6971
+ import { join as join7 } from "path";
6915
6972
  function buildMcpCommands(backendId) {
6916
6973
  const exe = backendId;
6917
6974
  return {
@@ -6972,7 +7029,8 @@ function wrapBackendAdapter(adapter) {
6972
7029
  sessionId: opts.sessionId,
6973
7030
  permMode: opts.permMode ?? "plan",
6974
7031
  allowedTools: opts.allowedTools ?? [],
6975
- cwd: opts.cwd
7032
+ cwd: opts.cwd,
7033
+ model: opts.model
6976
7034
  });
6977
7035
  return config2.args;
6978
7036
  },
@@ -7001,7 +7059,7 @@ function wrapBackendAdapter(adapter) {
7001
7059
  const configPath = writeMcpConfigFile(server);
7002
7060
  return ["--mcp-config", configPath];
7003
7061
  },
7004
- getSkillPath: () => join8(SKILLS_PATH, `agent-${adapter.id}.md`)
7062
+ getSkillPath: () => join7(SKILLS_PATH, `agent-${adapter.id}.md`)
7005
7063
  };
7006
7064
  }
7007
7065
  var BACKEND_CAPABILITIES;
@@ -7044,7 +7102,7 @@ var init_wrap_backend = __esm({
7044
7102
 
7045
7103
  // src/agents/runners/config-loader.ts
7046
7104
  import { readFileSync as readFileSync6, readdirSync as readdirSync4, existsSync as existsSync9, mkdirSync as mkdirSync4, watchFile, unwatchFile } from "fs";
7047
- import { join as join9 } from "path";
7105
+ import { join as join8 } from "path";
7048
7106
  import { execFileSync } from "child_process";
7049
7107
  function resolveExecutable(config2) {
7050
7108
  if (existsSync9(config2.executable)) return config2.executable;
@@ -7180,7 +7238,7 @@ function configToRunner(config2) {
7180
7238
  prepareMcpInjection() {
7181
7239
  return [];
7182
7240
  },
7183
- getSkillPath: () => join9(SKILLS_PATH, `agent-${config2.id}.md`)
7241
+ getSkillPath: () => join8(SKILLS_PATH, `agent-${config2.id}.md`)
7184
7242
  };
7185
7243
  }
7186
7244
  function loadRunnerConfig(filePath) {
@@ -7200,7 +7258,7 @@ function loadAllRunnerConfigs() {
7200
7258
  const files = readdirSync4(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
7201
7259
  const configs = [];
7202
7260
  for (const file of files) {
7203
- const config2 = loadRunnerConfig(join9(RUNNERS_PATH, file));
7261
+ const config2 = loadRunnerConfig(join8(RUNNERS_PATH, file));
7204
7262
  if (config2) configs.push(config2);
7205
7263
  }
7206
7264
  return configs;
@@ -7230,7 +7288,7 @@ function watchRunnerConfigs(onChange) {
7230
7288
  }
7231
7289
  const files = readdirSync4(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
7232
7290
  for (const file of files) {
7233
- const fullPath = join9(RUNNERS_PATH, file);
7291
+ const fullPath = join8(RUNNERS_PATH, file);
7234
7292
  if (watchedFiles.has(fullPath)) continue;
7235
7293
  watchedFiles.add(fullPath);
7236
7294
  watchFile(fullPath, { interval: 5e3 }, () => {
@@ -7571,6 +7629,8 @@ var init_telegram2 = __esm({
7571
7629
  return;
7572
7630
  }
7573
7631
  const data = ctx.callbackQuery.data;
7632
+ await ctx.answerCallbackQuery().catch(() => {
7633
+ });
7574
7634
  try {
7575
7635
  for (const handler2 of this.callbackHandlers) {
7576
7636
  await handler2(chatId, data, this);
@@ -7578,8 +7638,6 @@ var init_telegram2 = __esm({
7578
7638
  } catch (err) {
7579
7639
  error("[telegram] Callback handler error:", err);
7580
7640
  }
7581
- await ctx.answerCallbackQuery().catch(() => {
7582
- });
7583
7641
  });
7584
7642
  this.bot.catch((err) => {
7585
7643
  error("[telegram] Unhandled error:", err);
@@ -7599,11 +7657,12 @@ var init_telegram2 = __esm({
7599
7657
  async sendTyping(chatId) {
7600
7658
  await this.bot.api.sendChatAction(numericChatId(chatId), "typing");
7601
7659
  }
7602
- async sendText(chatId, text, parseMode) {
7660
+ async sendText(chatId, text, parseMode, threadId) {
7661
+ const threadOpts = threadId ? { message_thread_id: threadId } : {};
7603
7662
  if (parseMode === "plain") {
7604
7663
  const plainChunks = splitMessage(text);
7605
7664
  for (const chunk of plainChunks) {
7606
- await this.bot.api.sendMessage(numericChatId(chatId), chunk);
7665
+ await this.bot.api.sendMessage(numericChatId(chatId), chunk, { ...threadOpts });
7607
7666
  }
7608
7667
  return;
7609
7668
  }
@@ -7612,12 +7671,14 @@ var init_telegram2 = __esm({
7612
7671
  for (const chunk of chunks) {
7613
7672
  try {
7614
7673
  await this.bot.api.sendMessage(numericChatId(chatId), chunk, {
7615
- parse_mode: "HTML"
7674
+ parse_mode: "HTML",
7675
+ ...threadOpts
7616
7676
  });
7617
7677
  } catch {
7618
7678
  await this.bot.api.sendMessage(
7619
7679
  numericChatId(chatId),
7620
- chunk.replace(/<[^>]+>/g, "")
7680
+ chunk.replace(/<[^>]+>/g, ""),
7681
+ { ...threadOpts }
7621
7682
  );
7622
7683
  }
7623
7684
  }
@@ -7642,7 +7703,9 @@ var init_telegram2 = __esm({
7642
7703
  }
7643
7704
  async sendTextReturningId(chatId, text, parseMode) {
7644
7705
  try {
7645
- const msg = await this.bot.api.sendMessage(numericChatId(chatId), text);
7706
+ const formatted = parseMode === "html" ? text : parseMode === "plain" ? text : formatForTelegram(text);
7707
+ const opts = parseMode === "plain" ? {} : { parse_mode: "HTML" };
7708
+ const msg = await this.bot.api.sendMessage(numericChatId(chatId), formatted, opts);
7646
7709
  return msg.message_id.toString();
7647
7710
  } catch {
7648
7711
  return void 0;
@@ -7816,13 +7879,13 @@ __export(discover_exports, {
7816
7879
  });
7817
7880
  import { readdir, readFile as readFile2 } from "fs/promises";
7818
7881
  import { createHash } from "crypto";
7819
- import { homedir as homedir3 } from "os";
7820
- import { join as join10 } from "path";
7882
+ import { homedir as homedir4 } from "os";
7883
+ import { join as join9 } from "path";
7821
7884
  async function discoverAllSkills() {
7822
7885
  const rawSkills = [];
7823
7886
  rawSkills.push(...await scanSkillDir(SKILLS_PATH, "cc-claw"));
7824
7887
  for (const backendId of getAllBackendIds()) {
7825
- const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join10(homedir3(), `.${backendId}`, "skills")];
7888
+ const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join9(homedir4(), `.${backendId}`, "skills")];
7826
7889
  for (const dir of dirs) {
7827
7890
  rawSkills.push(...await scanSkillDir(dir, backendId));
7828
7891
  }
@@ -7842,7 +7905,7 @@ async function scanSkillDir(skillsDir, source) {
7842
7905
  let content;
7843
7906
  let resolvedPath;
7844
7907
  for (const candidate of SKILL_FILE_CANDIDATES) {
7845
- const p = join10(skillsDir, entry.name, candidate);
7908
+ const p = join9(skillsDir, entry.name, candidate);
7846
7909
  try {
7847
7910
  content = await readFile2(p, "utf-8");
7848
7911
  resolvedPath = p;
@@ -7945,11 +8008,11 @@ var init_discover = __esm({
7945
8008
  init_backends();
7946
8009
  SKILL_FILE_CANDIDATES = ["SKILL.md", "skill.md"];
7947
8010
  BACKEND_SKILL_DIRS = {
7948
- claude: [join10(homedir3(), ".claude", "skills")],
7949
- gemini: [join10(homedir3(), ".gemini", "skills")],
8011
+ claude: [join9(homedir4(), ".claude", "skills")],
8012
+ gemini: [join9(homedir4(), ".gemini", "skills")],
7950
8013
  codex: [
7951
- join10(homedir3(), ".agents", "skills"),
7952
- join10(homedir3(), ".codex", "skills")
8014
+ join9(homedir4(), ".agents", "skills"),
8015
+ join9(homedir4(), ".codex", "skills")
7953
8016
  ]
7954
8017
  };
7955
8018
  }
@@ -7962,7 +8025,7 @@ __export(install_exports, {
7962
8025
  });
7963
8026
  import { mkdir, readdir as readdir2, readFile as readFile3, cp } from "fs/promises";
7964
8027
  import { existsSync as existsSync10 } from "fs";
7965
- import { join as join11, basename } from "path";
8028
+ import { join as join10, basename } from "path";
7966
8029
  import { execSync as execSync4 } from "child_process";
7967
8030
  async function installSkillFromGitHub(urlOrShorthand) {
7968
8031
  let repoUrl;
@@ -7973,23 +8036,23 @@ async function installSkillFromGitHub(urlOrShorthand) {
7973
8036
  }
7974
8037
  repoUrl = parsed.cloneUrl;
7975
8038
  subPath = parsed.subPath;
7976
- const tmpDir = join11("/tmp", `cc-claw-skill-${Date.now()}`);
8039
+ const tmpDir = join10("/tmp", `cc-claw-skill-${Date.now()}`);
7977
8040
  try {
7978
8041
  log(`[skill-install] Cloning ${repoUrl} to ${tmpDir}`);
7979
8042
  execSync4(`git clone --depth 1 ${repoUrl} ${tmpDir}`, {
7980
8043
  stdio: "pipe",
7981
8044
  timeout: 3e4
7982
8045
  });
7983
- if (!existsSync10(join11(tmpDir, ".git"))) {
8046
+ if (!existsSync10(join10(tmpDir, ".git"))) {
7984
8047
  return { success: false, error: "Git clone failed: no .git directory produced" };
7985
8048
  }
7986
- const searchRoot = subPath ? join11(tmpDir, subPath) : tmpDir;
8049
+ const searchRoot = subPath ? join10(tmpDir, subPath) : tmpDir;
7987
8050
  const skillDir = await findSkillDir(searchRoot);
7988
8051
  if (!skillDir) {
7989
8052
  return { success: false, error: "No SKILL.md found in the repository." };
7990
8053
  }
7991
8054
  const skillFolderName = basename(skillDir);
7992
- const destDir = join11(SKILLS_PATH, skillFolderName);
8055
+ const destDir = join10(SKILLS_PATH, skillFolderName);
7993
8056
  if (existsSync10(destDir)) {
7994
8057
  log(`[skill-install] Overwriting existing skill at ${destDir}`);
7995
8058
  }
@@ -7997,12 +8060,12 @@ async function installSkillFromGitHub(urlOrShorthand) {
7997
8060
  await cp(skillDir, destDir, { recursive: true });
7998
8061
  let skillName = skillFolderName;
7999
8062
  try {
8000
- const content = await readFile3(join11(destDir, "SKILL.md"), "utf-8");
8063
+ const content = await readFile3(join10(destDir, "SKILL.md"), "utf-8");
8001
8064
  const nameMatch = content.match(/^name:\s*(.+)$/m);
8002
8065
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
8003
8066
  } catch {
8004
8067
  try {
8005
- const content = await readFile3(join11(destDir, "skill.md"), "utf-8");
8068
+ const content = await readFile3(join10(destDir, "skill.md"), "utf-8");
8006
8069
  const nameMatch = content.match(/^name:\s*(.+)$/m);
8007
8070
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
8008
8071
  } catch {
@@ -8037,15 +8100,15 @@ function parseGitHubUrl(input) {
8037
8100
  async function findSkillDir(root) {
8038
8101
  const candidates = ["SKILL.md", "skill.md"];
8039
8102
  for (const c of candidates) {
8040
- if (existsSync10(join11(root, c))) return root;
8103
+ if (existsSync10(join10(root, c))) return root;
8041
8104
  }
8042
8105
  try {
8043
8106
  const entries = await readdir2(root, { withFileTypes: true });
8044
8107
  for (const entry of entries) {
8045
8108
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8046
8109
  for (const c of candidates) {
8047
- if (existsSync10(join11(root, entry.name, c))) {
8048
- return join11(root, entry.name);
8110
+ if (existsSync10(join10(root, entry.name, c))) {
8111
+ return join10(root, entry.name);
8049
8112
  }
8050
8113
  }
8051
8114
  }
@@ -8057,15 +8120,15 @@ async function findSkillDir(root) {
8057
8120
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8058
8121
  let subEntries;
8059
8122
  try {
8060
- subEntries = await readdir2(join11(root, entry.name), { withFileTypes: true });
8123
+ subEntries = await readdir2(join10(root, entry.name), { withFileTypes: true });
8061
8124
  } catch {
8062
8125
  continue;
8063
8126
  }
8064
8127
  for (const sub of subEntries) {
8065
8128
  if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
8066
8129
  for (const c of candidates) {
8067
- if (existsSync10(join11(root, entry.name, sub.name, c))) {
8068
- return join11(root, entry.name, sub.name);
8130
+ if (existsSync10(join10(root, entry.name, sub.name, c))) {
8131
+ return join10(root, entry.name, sub.name);
8069
8132
  }
8070
8133
  }
8071
8134
  }
@@ -8084,7 +8147,7 @@ var init_install = __esm({
8084
8147
 
8085
8148
  // src/bootstrap/profile.ts
8086
8149
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync11 } from "fs";
8087
- import { join as join12 } from "path";
8150
+ import { join as join11 } from "path";
8088
8151
  function hasActiveProfile(chatId) {
8089
8152
  return activeProfiles.has(chatId);
8090
8153
  }
@@ -8229,14 +8292,14 @@ var init_profile = __esm({
8229
8292
  "use strict";
8230
8293
  init_paths();
8231
8294
  init_log();
8232
- USER_PATH2 = join12(WORKSPACE_PATH, "USER.md");
8295
+ USER_PATH2 = join11(WORKSPACE_PATH, "USER.md");
8233
8296
  activeProfiles = /* @__PURE__ */ new Map();
8234
8297
  }
8235
8298
  });
8236
8299
 
8237
8300
  // src/bootstrap/heartbeat.ts
8238
8301
  import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
8239
- import { join as join13 } from "path";
8302
+ import { join as join12 } from "path";
8240
8303
  function initHeartbeat(channelReg) {
8241
8304
  registry2 = channelReg;
8242
8305
  }
@@ -8244,8 +8307,18 @@ function startHeartbeatForChat(chatId) {
8244
8307
  stopHeartbeatForChat(chatId);
8245
8308
  const config2 = getHeartbeatConfig(chatId);
8246
8309
  if (!config2 || !config2.enabled) return;
8310
+ let running = false;
8247
8311
  const timer = setInterval(async () => {
8248
- await runHeartbeat(chatId, config2);
8312
+ if (running) {
8313
+ log(`[heartbeat] Skipping tick for ${chatId}: previous heartbeat still running`);
8314
+ return;
8315
+ }
8316
+ running = true;
8317
+ try {
8318
+ await runHeartbeat(chatId, config2);
8319
+ } finally {
8320
+ running = false;
8321
+ }
8249
8322
  }, config2.intervalMs);
8250
8323
  activeTimers2.set(chatId, timer);
8251
8324
  const nextBeat = new Date(Date.now() + config2.intervalMs).toISOString();
@@ -8412,7 +8485,7 @@ var init_heartbeat = __esm({
8412
8485
  init_backends();
8413
8486
  init_health2();
8414
8487
  init_log();
8415
- HEARTBEAT_MD_PATH = join13(WORKSPACE_PATH, "HEARTBEAT.md");
8488
+ HEARTBEAT_MD_PATH = join12(WORKSPACE_PATH, "HEARTBEAT.md");
8416
8489
  HEARTBEAT_OK = "HEARTBEAT_OK";
8417
8490
  registry2 = null;
8418
8491
  activeTimers2 = /* @__PURE__ */ new Map();
@@ -8458,8 +8531,9 @@ var init_format_time = __esm({
8458
8531
  });
8459
8532
 
8460
8533
  // src/media/image-gen.ts
8461
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync13 } from "fs";
8462
- import { join as join14 } from "path";
8534
+ import { mkdirSync as mkdirSync5, existsSync as existsSync13 } from "fs";
8535
+ import { writeFile as writeFile2 } from "fs/promises";
8536
+ import { join as join13 } from "path";
8463
8537
  async function generateImage(prompt) {
8464
8538
  const apiKey = process.env.GEMINI_API_KEY;
8465
8539
  if (!apiKey) {
@@ -8511,9 +8585,9 @@ async function generateImage(prompt) {
8511
8585
  }
8512
8586
  const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
8513
8587
  const filename = `img_${Date.now()}.${ext}`;
8514
- const filePath = join14(IMAGE_OUTPUT_DIR, filename);
8588
+ const filePath = join13(IMAGE_OUTPUT_DIR, filename);
8515
8589
  const buffer = Buffer.from(imageData, "base64");
8516
- writeFileSync5(filePath, buffer);
8590
+ await writeFile2(filePath, buffer);
8517
8591
  log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
8518
8592
  return { filePath, text: textResponse, mimeType };
8519
8593
  }
@@ -8526,8 +8600,8 @@ var init_image_gen = __esm({
8526
8600
  "use strict";
8527
8601
  init_log();
8528
8602
  IMAGE_MODEL = "gemini-3.1-flash-image-preview";
8529
- IMAGE_OUTPUT_DIR = join14(
8530
- process.env.CC_CLAW_HOME ?? join14(process.env.HOME ?? "/tmp", ".cc-claw"),
8603
+ IMAGE_OUTPUT_DIR = join13(
8604
+ process.env.CC_CLAW_HOME ?? join13(process.env.HOME ?? "/tmp", ".cc-claw"),
8531
8605
  "data",
8532
8606
  "images"
8533
8607
  );
@@ -8675,8 +8749,8 @@ async function mp3ToOgg(mp3Buffer) {
8675
8749
  const id = crypto.randomUUID();
8676
8750
  const tmpMp3 = `/tmp/cc-claw-tts-${id}.mp3`;
8677
8751
  const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
8678
- const { writeFile: writeFile4 } = await import("fs/promises");
8679
- await writeFile4(tmpMp3, mp3Buffer);
8752
+ const { writeFile: writeFile5 } = await import("fs/promises");
8753
+ await writeFile5(tmpMp3, mp3Buffer);
8680
8754
  await execFileAsync2("ffmpeg", ["-y", "-i", tmpMp3, "-c:a", "libopus", "-b:a", "64k", tmpOgg]);
8681
8755
  const oggBuffer = await readFile4(tmpOgg);
8682
8756
  unlink(tmpMp3).catch((err) => {
@@ -8691,8 +8765,7 @@ async function macOsTts(text, voice2 = "Samantha") {
8691
8765
  const id = crypto.randomUUID();
8692
8766
  const tmpAiff = `/tmp/cc-claw-tts-${id}.aiff`;
8693
8767
  const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
8694
- const sanitized = text.replace(/['"\\$`]/g, " ");
8695
- await execFileAsync2("say", ["-v", voice2, "-o", tmpAiff, sanitized]);
8768
+ await execFileAsync2("say", ["-v", voice2, "-o", tmpAiff, text]);
8696
8769
  await execFileAsync2("ffmpeg", ["-y", "-i", tmpAiff, "-c:a", "libopus", "-b:a", "64k", tmpOgg]);
8697
8770
  const oggBuffer = await readFile4(tmpOgg);
8698
8771
  unlink(tmpAiff).catch((err) => {
@@ -9567,7 +9640,7 @@ var init_backend_cmd = __esm({
9567
9640
  });
9568
9641
 
9569
9642
  // src/router.ts
9570
- import { readFile as readFile5, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
9643
+ import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2 } from "fs/promises";
9571
9644
  import { resolve as resolvePath } from "path";
9572
9645
  function parseMcpListOutput(output2) {
9573
9646
  const results = [];
@@ -10518,8 +10591,8 @@ Use /skills to see it.`, "plain");
10518
10591
  if (!lim.max_input_tokens) continue;
10519
10592
  const u = getBackendUsageInWindow(lim.backend, lim.window);
10520
10593
  const pct = (u.input_tokens / lim.max_input_tokens * 100).toFixed(0);
10521
- const warn3 = u.input_tokens / lim.max_input_tokens >= lim.warn_pct;
10522
- lines.push(` ${lim.backend} (${lim.window}): ${pct}% of ${(lim.max_input_tokens / 1e3).toFixed(0)}K${warn3 ? " \u26A0\uFE0F" : ""}`);
10594
+ const warn2 = u.input_tokens / lim.max_input_tokens >= lim.warn_pct;
10595
+ lines.push(` ${lim.backend} (${lim.window}): ${pct}% of ${(lim.max_input_tokens / 1e3).toFixed(0)}K${warn2 ? " \u26A0\uFE0F" : ""}`);
10523
10596
  }
10524
10597
  }
10525
10598
  await channel.sendText(chatId, lines.join("\n"), "plain");
@@ -10719,8 +10792,8 @@ Use /skills to see it.`, "plain");
10719
10792
  lines.push(` \u2705 <b>cc-claw</b> <i>Agent orchestrator (spawn, tasks, inbox)</i>`);
10720
10793
  }
10721
10794
  const { execFile: execFile5 } = await import("child_process");
10722
- const { homedir: homedir6 } = await import("os");
10723
- const discoveryCwd = homedir6();
10795
+ const { homedir: homedir7 } = await import("os");
10796
+ const discoveryCwd = homedir7();
10724
10797
  const runnerResults = await Promise.allSettled(
10725
10798
  getAllRunners().map((runner) => {
10726
10799
  const listCmd = runner.getMcpListCommand();
@@ -10912,8 +10985,10 @@ async function handleMedia(msg, channel) {
10912
10985
  const videoMime = msg.metadata?.mimeType ?? msg.mimeType ?? "video/mp4";
10913
10986
  const videoExt = videoMime.split("/")[1]?.replace("quicktime", "mov") || "mp4";
10914
10987
  const tempVideoPath = `/tmp/cc-claw-video-${Date.now()}.${videoExt}`;
10915
- await writeFile2(tempVideoPath, videoBuffer);
10988
+ await writeFile3(tempVideoPath, videoBuffer);
10916
10989
  let prompt2;
10990
+ const cleanupVideo = () => unlink2(tempVideoPath).catch(() => {
10991
+ });
10917
10992
  if (wantsVideoAnalysis(caption)) {
10918
10993
  const geminiPrompt = caption || "Analyze this video and describe what you see in detail.";
10919
10994
  let analysis;
@@ -10952,6 +11027,7 @@ Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
10952
11027
  const response2 = await askAgent(chatId, prompt2, { cwd: getCwd(chatId), model: vidModel, permMode: vMode, onToolAction: vidToolCb });
10953
11028
  if (response2.usage) addUsage(chatId, response2.usage.input, response2.usage.output, response2.usage.cacheRead, vidModel);
10954
11029
  await sendResponse(chatId, channel, response2.text, msg.messageId);
11030
+ cleanupVideo();
10955
11031
  return;
10956
11032
  }
10957
11033
  const fileBuffer = await channel.downloadFile(fileName);
@@ -10962,7 +11038,7 @@ Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
10962
11038
  if (msg.type === "photo" || isImageExt(ext)) {
10963
11039
  const imgExt = msg.type === "photo" ? "jpg" : ext || "jpg";
10964
11040
  tempFilePath = `/tmp/cc-claw-img-${Date.now()}.${imgExt}`;
10965
- await writeFile2(tempFilePath, fileBuffer);
11041
+ await writeFile3(tempFilePath, fileBuffer);
10966
11042
  prompt = caption ? `The user sent an image with caption: "${caption}"
10967
11043
 
10968
11044
  The image has been saved to: ${tempFilePath}
@@ -11403,6 +11479,9 @@ Your conversation history is preserved. ${targetAdapter.displayName} will receiv
11403
11479
  async function doBackendSwitch(chatId, backendId, channel) {
11404
11480
  const targetAdapter = getAdapter(backendId);
11405
11481
  const pairCount = getMessagePairCount(chatId);
11482
+ if (pairCount >= 2) {
11483
+ await channel.sendText(chatId, `\u23F3 Saving context...`, "plain");
11484
+ }
11406
11485
  const summarized = await summarizeWithFallbackChain(chatId, backendId);
11407
11486
  const bridge = buildContextBridge(chatId);
11408
11487
  if (bridge) {
@@ -11521,7 +11600,7 @@ ${PERM_MODES[chosen]}`,
11521
11600
  const pending = getPendingEscalation(chatId);
11522
11601
  if (pending) {
11523
11602
  removePendingEscalation(chatId);
11524
- await handleMessage({ text: pending, chatId, source: "telegram" }, channel);
11603
+ await handleMessage({ text: pending, chatId, source: "telegram", type: "text" }, channel);
11525
11604
  }
11526
11605
  } else if (data === "perm:deny") {
11527
11606
  removePendingEscalation(chatId);
@@ -11954,7 +12033,7 @@ var init_router = __esm({
11954
12033
  pendingFallbackMessages = /* @__PURE__ */ new Map();
11955
12034
  CLI_INSTALL_HINTS = {
11956
12035
  claude: "Install: npm install -g @anthropic-ai/claude-code",
11957
- gemini: "Install: npm install -g @anthropic-ai/gemini-cli",
12036
+ gemini: "Install: npm install -g @google/gemini-cli",
11958
12037
  codex: "Install: npm install -g @openai/codex"
11959
12038
  };
11960
12039
  BLOCKED_PATH_PATTERNS2 = [
@@ -12047,17 +12126,17 @@ var init_router = __esm({
12047
12126
 
12048
12127
  // src/skills/bootstrap.ts
12049
12128
  import { existsSync as existsSync14 } from "fs";
12050
- import { readdir as readdir3, readFile as readFile6, writeFile as writeFile3, copyFile } from "fs/promises";
12051
- import { join as join15, dirname as dirname3 } from "path";
12052
- import { fileURLToPath as fileURLToPath3 } from "url";
12129
+ import { readdir as readdir3, readFile as readFile6, writeFile as writeFile4, copyFile } from "fs/promises";
12130
+ import { join as join14, dirname as dirname2 } from "path";
12131
+ import { fileURLToPath as fileURLToPath2 } from "url";
12053
12132
  async function copyAgentManifestSkills() {
12054
12133
  if (!existsSync14(PKG_SKILLS)) return;
12055
12134
  try {
12056
12135
  const entries = await readdir3(PKG_SKILLS, { withFileTypes: true });
12057
12136
  for (const entry of entries) {
12058
12137
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
12059
- const src = join15(PKG_SKILLS, entry.name);
12060
- const dest = join15(SKILLS_PATH, entry.name);
12138
+ const src = join14(PKG_SKILLS, entry.name);
12139
+ const dest = join14(SKILLS_PATH, entry.name);
12061
12140
  if (existsSync14(dest)) continue;
12062
12141
  await copyFile(src, dest);
12063
12142
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
@@ -12068,7 +12147,7 @@ async function copyAgentManifestSkills() {
12068
12147
  }
12069
12148
  async function bootstrapSkills() {
12070
12149
  await copyAgentManifestSkills();
12071
- const usmDir = join15(SKILLS_PATH, USM_DIR_NAME);
12150
+ const usmDir = join14(SKILLS_PATH, USM_DIR_NAME);
12072
12151
  if (existsSync14(usmDir)) return;
12073
12152
  try {
12074
12153
  const entries = await readdir3(SKILLS_PATH);
@@ -12091,7 +12170,7 @@ async function bootstrapSkills() {
12091
12170
  }
12092
12171
  }
12093
12172
  async function patchUsmForCcClaw(usmDir) {
12094
- const skillPath = join15(usmDir, "SKILL.md");
12173
+ const skillPath = join14(usmDir, "SKILL.md");
12095
12174
  if (!existsSync14(skillPath)) return;
12096
12175
  try {
12097
12176
  let content = await readFile6(skillPath, "utf-8");
@@ -12120,7 +12199,7 @@ async function patchUsmForCcClaw(usmDir) {
12120
12199
  }
12121
12200
  }
12122
12201
  if (patched) {
12123
- await writeFile3(skillPath, content, "utf-8");
12202
+ await writeFile4(skillPath, content, "utf-8");
12124
12203
  log("[skills] Patched USM SKILL.md with CC-Claw support");
12125
12204
  }
12126
12205
  } catch (err) {
@@ -12137,8 +12216,8 @@ var init_bootstrap = __esm({
12137
12216
  USM_REPO = "jacob-bd/universal-skills-manager";
12138
12217
  USM_DIR_NAME = "universal-skills-manager";
12139
12218
  CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
12140
- PKG_ROOT = join15(dirname3(fileURLToPath3(import.meta.url)), "..", "..");
12141
- PKG_SKILLS = join15(PKG_ROOT, "skills");
12219
+ PKG_ROOT = join14(dirname2(fileURLToPath2(import.meta.url)), "..", "..");
12220
+ PKG_SKILLS = join14(PKG_ROOT, "skills");
12142
12221
  }
12143
12222
  });
12144
12223
 
@@ -12352,9 +12431,9 @@ __export(ai_skill_exports, {
12352
12431
  generateAiSkill: () => generateAiSkill,
12353
12432
  installAiSkill: () => installAiSkill
12354
12433
  });
12355
- import { existsSync as existsSync15, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
12356
- import { join as join16 } from "path";
12357
- import { homedir as homedir4 } from "os";
12434
+ import { existsSync as existsSync15, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
12435
+ import { join as join15 } from "path";
12436
+ import { homedir as homedir5 } from "os";
12358
12437
  function generateAiSkill() {
12359
12438
  const version = VERSION;
12360
12439
  let systemState = "";
@@ -12654,11 +12733,11 @@ function installAiSkill() {
12654
12733
  const failed = [];
12655
12734
  for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
12656
12735
  for (const dir of dirs) {
12657
- const skillDir = join16(dir, "cc-claw-cli");
12658
- const skillPath = join16(skillDir, "SKILL.md");
12736
+ const skillDir = join15(dir, "cc-claw-cli");
12737
+ const skillPath = join15(skillDir, "SKILL.md");
12659
12738
  try {
12660
12739
  mkdirSync6(skillDir, { recursive: true });
12661
- writeFileSync6(skillPath, skill, "utf-8");
12740
+ writeFileSync5(skillPath, skill, "utf-8");
12662
12741
  installed.push(skillPath);
12663
12742
  } catch {
12664
12743
  failed.push(skillPath);
@@ -12674,10 +12753,10 @@ var init_ai_skill = __esm({
12674
12753
  init_paths();
12675
12754
  init_version();
12676
12755
  BACKEND_SKILL_DIRS2 = {
12677
- "cc-claw": [join16(homedir4(), ".cc-claw", "workspace", "skills")],
12678
- claude: [join16(homedir4(), ".claude", "skills")],
12679
- gemini: [join16(homedir4(), ".gemini", "skills")],
12680
- codex: [join16(homedir4(), ".agents", "skills")]
12756
+ "cc-claw": [join15(homedir5(), ".cc-claw", "workspace", "skills")],
12757
+ claude: [join15(homedir5(), ".claude", "skills")],
12758
+ gemini: [join15(homedir5(), ".gemini", "skills")],
12759
+ codex: [join15(homedir5(), ".agents", "skills")]
12681
12760
  };
12682
12761
  }
12683
12762
  });
@@ -12688,17 +12767,17 @@ __export(index_exports, {
12688
12767
  main: () => main
12689
12768
  });
12690
12769
  import { mkdirSync as mkdirSync7, existsSync as existsSync16, renameSync, statSync as statSync2, readFileSync as readFileSync10 } from "fs";
12691
- import { join as join17 } from "path";
12770
+ import { join as join16 } from "path";
12692
12771
  import dotenv from "dotenv";
12693
12772
  function migrateLayout() {
12694
12773
  const moves = [
12695
- [join17(CC_CLAW_HOME, "cc-claw.db"), join17(DATA_PATH, "cc-claw.db")],
12696
- [join17(CC_CLAW_HOME, "cc-claw.db-shm"), join17(DATA_PATH, "cc-claw.db-shm")],
12697
- [join17(CC_CLAW_HOME, "cc-claw.db-wal"), join17(DATA_PATH, "cc-claw.db-wal")],
12698
- [join17(CC_CLAW_HOME, "cc-claw.log"), join17(LOGS_PATH, "cc-claw.log")],
12699
- [join17(CC_CLAW_HOME, "cc-claw.log.1"), join17(LOGS_PATH, "cc-claw.log.1")],
12700
- [join17(CC_CLAW_HOME, "cc-claw.error.log"), join17(LOGS_PATH, "cc-claw.error.log")],
12701
- [join17(CC_CLAW_HOME, "cc-claw.error.log.1"), join17(LOGS_PATH, "cc-claw.error.log.1")]
12774
+ [join16(CC_CLAW_HOME, "cc-claw.db"), join16(DATA_PATH, "cc-claw.db")],
12775
+ [join16(CC_CLAW_HOME, "cc-claw.db-shm"), join16(DATA_PATH, "cc-claw.db-shm")],
12776
+ [join16(CC_CLAW_HOME, "cc-claw.db-wal"), join16(DATA_PATH, "cc-claw.db-wal")],
12777
+ [join16(CC_CLAW_HOME, "cc-claw.log"), join16(LOGS_PATH, "cc-claw.log")],
12778
+ [join16(CC_CLAW_HOME, "cc-claw.log.1"), join16(LOGS_PATH, "cc-claw.log.1")],
12779
+ [join16(CC_CLAW_HOME, "cc-claw.error.log"), join16(LOGS_PATH, "cc-claw.error.log")],
12780
+ [join16(CC_CLAW_HOME, "cc-claw.error.log.1"), join16(LOGS_PATH, "cc-claw.error.log.1")]
12702
12781
  ];
12703
12782
  for (const [from, to] of moves) {
12704
12783
  if (existsSync16(from) && !existsSync16(to)) {
@@ -12803,11 +12882,11 @@ async function main() {
12803
12882
  bootstrapSkills().catch((err) => error("[cc-claw] Skill bootstrap failed:", err));
12804
12883
  try {
12805
12884
  const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
12806
- const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync11 } = await import("fs");
12807
- const { join: join20 } = await import("path");
12808
- const skillDir = join20(SKILLS_PATH, "cc-claw-cli");
12885
+ const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync11 } = await import("fs");
12886
+ const { join: join19 } = await import("path");
12887
+ const skillDir = join19(SKILLS_PATH, "cc-claw-cli");
12809
12888
  mkdirSync11(skillDir, { recursive: true });
12810
- writeFileSync9(join20(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
12889
+ writeFileSync8(join19(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
12811
12890
  log("[cc-claw] AI skill updated");
12812
12891
  } catch {
12813
12892
  }
@@ -12833,7 +12912,13 @@ async function main() {
12833
12912
  stopMonitor();
12834
12913
  shutdownOrchestrator();
12835
12914
  shutdownScheduler();
12836
- await summarizeAllPending();
12915
+ await Promise.race([
12916
+ summarizeAllPending(),
12917
+ new Promise((resolve) => setTimeout(() => {
12918
+ log("[cc-claw] Summarization timed out after 15s, skipping.");
12919
+ resolve();
12920
+ }, 15e3))
12921
+ ]);
12837
12922
  await channelRegistry.stopAll();
12838
12923
  log("[cc-claw] Clean shutdown complete.");
12839
12924
  } catch (err) {
@@ -12891,10 +12976,10 @@ __export(service_exports, {
12891
12976
  serviceStatus: () => serviceStatus,
12892
12977
  uninstallService: () => uninstallService
12893
12978
  });
12894
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
12979
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
12895
12980
  import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
12896
- import { homedir as homedir5, platform } from "os";
12897
- import { join as join18, dirname as dirname4 } from "path";
12981
+ import { homedir as homedir6, platform } from "os";
12982
+ import { join as join17, dirname as dirname3 } from "path";
12898
12983
  function resolveExecutable2(name) {
12899
12984
  try {
12900
12985
  return execFileSync2("which", [name], { encoding: "utf-8" }).trim();
@@ -12903,18 +12988,18 @@ function resolveExecutable2(name) {
12903
12988
  }
12904
12989
  }
12905
12990
  function getPathDirs() {
12906
- const nodeBin = dirname4(process.execPath);
12907
- const home = homedir5();
12991
+ const nodeBin = dirname3(process.execPath);
12992
+ const home = homedir6();
12908
12993
  const dirs = /* @__PURE__ */ new Set([
12909
12994
  nodeBin,
12910
- join18(home, ".local", "bin"),
12995
+ join17(home, ".local", "bin"),
12911
12996
  "/usr/local/bin",
12912
12997
  "/usr/bin",
12913
12998
  "/bin"
12914
12999
  ]);
12915
13000
  try {
12916
13001
  const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
12917
- if (prefix) dirs.add(join18(prefix, "bin"));
13002
+ if (prefix) dirs.add(join17(prefix, "bin"));
12918
13003
  } catch {
12919
13004
  }
12920
13005
  return [...dirs].join(":");
@@ -12922,7 +13007,7 @@ function getPathDirs() {
12922
13007
  function generatePlist() {
12923
13008
  const ccClawBin = resolveExecutable2("cc-claw");
12924
13009
  const pathDirs = getPathDirs();
12925
- const home = homedir5();
13010
+ const home = homedir6();
12926
13011
  return `<?xml version="1.0" encoding="UTF-8"?>
12927
13012
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
12928
13013
  <plist version="1.0">
@@ -12968,7 +13053,7 @@ function generatePlist() {
12968
13053
  </plist>`;
12969
13054
  }
12970
13055
  function installMacOS() {
12971
- const agentsDir = dirname4(PLIST_PATH);
13056
+ const agentsDir = dirname3(PLIST_PATH);
12972
13057
  if (!existsSync17(agentsDir)) mkdirSync8(agentsDir, { recursive: true });
12973
13058
  if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
12974
13059
  if (existsSync17(PLIST_PATH)) {
@@ -12977,7 +13062,7 @@ function installMacOS() {
12977
13062
  } catch {
12978
13063
  }
12979
13064
  }
12980
- writeFileSync7(PLIST_PATH, generatePlist());
13065
+ writeFileSync6(PLIST_PATH, generatePlist());
12981
13066
  console.log(` Installed: ${PLIST_PATH}`);
12982
13067
  execFileSync2("launchctl", ["load", PLIST_PATH]);
12983
13068
  console.log(" Service loaded and starting.");
@@ -13027,7 +13112,7 @@ Restart=on-failure
13027
13112
  RestartSec=10
13028
13113
  WorkingDirectory=${CC_CLAW_HOME}
13029
13114
  Environment=PATH=${pathDirs}
13030
- Environment=HOME=${homedir5()}
13115
+ Environment=HOME=${homedir6()}
13031
13116
 
13032
13117
  [Install]
13033
13118
  WantedBy=default.target
@@ -13036,7 +13121,7 @@ WantedBy=default.target
13036
13121
  function installLinux() {
13037
13122
  if (!existsSync17(SYSTEMD_DIR)) mkdirSync8(SYSTEMD_DIR, { recursive: true });
13038
13123
  if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
13039
- writeFileSync7(UNIT_PATH, generateUnit());
13124
+ writeFileSync6(UNIT_PATH, generateUnit());
13040
13125
  console.log(` Installed: ${UNIT_PATH}`);
13041
13126
  execFileSync2("systemctl", ["--user", "daemon-reload"]);
13042
13127
  execFileSync2("systemctl", ["--user", "enable", "cc-claw"]);
@@ -13069,7 +13154,7 @@ function statusLinux() {
13069
13154
  }
13070
13155
  }
13071
13156
  function installService() {
13072
- if (!existsSync17(join18(CC_CLAW_HOME, ".env"))) {
13157
+ if (!existsSync17(join17(CC_CLAW_HOME, ".env"))) {
13073
13158
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
13074
13159
  console.error(" Run 'cc-claw setup' before installing the service.");
13075
13160
  process.exitCode = 1;
@@ -13098,9 +13183,9 @@ var init_service = __esm({
13098
13183
  "use strict";
13099
13184
  init_paths();
13100
13185
  PLIST_LABEL = "com.cc-claw";
13101
- PLIST_PATH = join18(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
13102
- SYSTEMD_DIR = join18(homedir5(), ".config", "systemd", "user");
13103
- UNIT_PATH = join18(SYSTEMD_DIR, "cc-claw.service");
13186
+ PLIST_PATH = join17(homedir6(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
13187
+ SYSTEMD_DIR = join17(homedir6(), ".config", "systemd", "user");
13188
+ UNIT_PATH = join17(SYSTEMD_DIR, "cc-claw.service");
13104
13189
  }
13105
13190
  });
13106
13191
 
@@ -13761,15 +13846,14 @@ async function logsCommand(opts) {
13761
13846
  console.log(tailLines.join("\n"));
13762
13847
  if (opts.follow) {
13763
13848
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
13764
- let lastSize = Buffer.byteLength(content, "utf-8");
13849
+ let lastLength = content.length;
13765
13850
  watchFile2(logFile, { interval: 500 }, () => {
13766
13851
  try {
13767
13852
  const newContent = readFileSync14(logFile, "utf-8");
13768
- const newSize = Buffer.byteLength(newContent, "utf-8");
13769
- if (newSize > lastSize) {
13770
- const newPart = newContent.slice(content.length);
13853
+ if (newContent.length > lastLength) {
13854
+ const newPart = newContent.slice(lastLength);
13771
13855
  process.stdout.write(newPart);
13772
- lastSize = newSize;
13856
+ lastLength = newContent.length;
13773
13857
  }
13774
13858
  } catch {
13775
13859
  }
@@ -14242,8 +14326,17 @@ async function cronEdit(globalOpts, id, opts) {
14242
14326
  values.push(opts.at);
14243
14327
  }
14244
14328
  if (opts.every) {
14245
- updates.push("every_ms = ?, schedule_type = 'every'");
14246
- values.push(opts.every);
14329
+ const m = opts.every.match(/^(\d+)\s*(m|min|h|hr|s|sec)$/i);
14330
+ if (m) {
14331
+ const num = parseInt(m[1], 10);
14332
+ const unit = m[2].toLowerCase();
14333
+ const ms = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
14334
+ updates.push("every_ms = ?, schedule_type = 'every'");
14335
+ values.push(ms);
14336
+ } else {
14337
+ updates.push("every_ms = ?, schedule_type = 'every'");
14338
+ values.push(parseInt(opts.every, 10) || 0);
14339
+ }
14247
14340
  }
14248
14341
  if (opts.backend) {
14249
14342
  updates.push("backend = ?");
@@ -14495,7 +14588,7 @@ __export(db_exports, {
14495
14588
  dbStats: () => dbStats
14496
14589
  });
14497
14590
  import { existsSync as existsSync27, statSync as statSync5, copyFileSync, mkdirSync as mkdirSync9 } from "fs";
14498
- import { dirname as dirname5 } from "path";
14591
+ import { dirname as dirname4 } from "path";
14499
14592
  async function dbStats(globalOpts) {
14500
14593
  if (!existsSync27(DB_PATH)) {
14501
14594
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
@@ -14545,7 +14638,7 @@ async function dbBackup(globalOpts, destPath) {
14545
14638
  }
14546
14639
  const dest = destPath ?? `${DB_PATH}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
14547
14640
  try {
14548
- mkdirSync9(dirname5(dest), { recursive: true });
14641
+ mkdirSync9(dirname4(dest), { recursive: true });
14549
14642
  copyFileSync(DB_PATH, dest);
14550
14643
  const walPath = DB_PATH + "-wal";
14551
14644
  if (existsSync27(walPath)) copyFileSync(walPath, dest + "-wal");
@@ -15917,7 +16010,7 @@ var init_completion = __esm({
15917
16010
  model: ["list", "get", "set"],
15918
16011
  thinking: ["get", "set"],
15919
16012
  summarizer: ["get", "set"],
15920
- memory: ["list", "search", "add", "forget", "history"],
16013
+ memory: ["list", "search", "history"],
15921
16014
  cron: ["list", "create", "edit", "cancel", "pause", "resume", "run", "runs", "health"],
15922
16015
  schedule: ["list", "create", "edit", "cancel", "pause", "resume", "run", "runs", "health"],
15923
16016
  agents: ["list", "spawn", "cancel", "cancel-all"],
@@ -15935,7 +16028,7 @@ var init_completion = __esm({
15935
16028
  chats: ["list", "alias", "remove-alias"],
15936
16029
  skills: ["list", "install"],
15937
16030
  mcps: ["list"],
15938
- db: ["stats", "path", "backup", "prune"],
16031
+ db: ["stats", "path", "backup"],
15939
16032
  chat: ["send", "stop"]
15940
16033
  };
15941
16034
  }
@@ -15943,10 +16036,10 @@ var init_completion = __esm({
15943
16036
 
15944
16037
  // src/setup.ts
15945
16038
  var setup_exports = {};
15946
- import { existsSync as existsSync38, writeFileSync as writeFileSync8, readFileSync as readFileSync17, copyFileSync as copyFileSync2, mkdirSync as mkdirSync10, statSync as statSync6 } from "fs";
16039
+ import { existsSync as existsSync38, writeFileSync as writeFileSync7, readFileSync as readFileSync17, copyFileSync as copyFileSync2, mkdirSync as mkdirSync10, statSync as statSync6 } from "fs";
15947
16040
  import { execFileSync as execFileSync4 } from "child_process";
15948
16041
  import { createInterface as createInterface5 } from "readline";
15949
- import { join as join19 } from "path";
16042
+ import { join as join18 } from "path";
15950
16043
  function divider2() {
15951
16044
  console.log(dim("\u2500".repeat(55)));
15952
16045
  }
@@ -16033,7 +16126,7 @@ async function setup() {
16033
16126
  if (match) env[match[1].trim()] = match[2].trim();
16034
16127
  }
16035
16128
  }
16036
- const cwdDb = join19(process.cwd(), "cc-claw.db");
16129
+ const cwdDb = join18(process.cwd(), "cc-claw.db");
16037
16130
  if (existsSync38(cwdDb) && !existsSync38(DB_PATH)) {
16038
16131
  const { size } = statSync6(cwdDb);
16039
16132
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
@@ -16247,7 +16340,7 @@ async function setup() {
16247
16340
  envLines.push("", "# Video Analysis", `GEMINI_API_KEY=${env.GEMINI_API_KEY}`);
16248
16341
  }
16249
16342
  const envContent = envLines.join("\n") + "\n";
16250
- writeFileSync8(ENV_PATH, envContent, { mode: 384 });
16343
+ writeFileSync7(ENV_PATH, envContent, { mode: 384 });
16251
16344
  console.log(green(` Config saved to ${ENV_PATH} (permissions: owner-only)`));
16252
16345
  header(6, TOTAL_STEPS, "Run on Startup (Daemon)");
16253
16346
  console.log(" CC-Claw can run automatically in the background, starting");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "CC-Claw: Personal AI assistant on Telegram — multi-backend (Claude, Gemini, Codex), sub-agent orchestration, MCP management",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",