cc-claw 0.24.2 → 0.25.0

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 +170 -30
  2. package/package.json +2 -2
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.24.2" : (() => {
36
+ VERSION = true ? "0.25.0" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -4314,19 +4314,21 @@ function parseDbTimestamp(ts2) {
4314
4314
  }
4315
4315
  function appendToLog(chatId, userMessage, assistantResponse, backend2, model2, sessionId, messageType, toolName, toolInput, toolOutput) {
4316
4316
  const now = Date.now();
4317
- appendMessageLog(chatId, "user", userMessage, backend2 ?? null, model2 ?? null, sessionId ?? null);
4318
- appendMessageLog(
4319
- chatId,
4320
- "assistant",
4321
- assistantResponse,
4322
- backend2 ?? null,
4323
- model2 ?? null,
4324
- sessionId ?? null,
4325
- messageType ?? null,
4326
- toolName ?? null,
4327
- toolInput ?? null,
4328
- toolOutput ?? null
4329
- );
4317
+ getDb().transaction(() => {
4318
+ appendMessageLog(chatId, "user", userMessage, backend2 ?? null, model2 ?? null, sessionId ?? null);
4319
+ appendMessageLog(
4320
+ chatId,
4321
+ "assistant",
4322
+ assistantResponse,
4323
+ backend2 ?? null,
4324
+ model2 ?? null,
4325
+ sessionId ?? null,
4326
+ messageType ?? null,
4327
+ toolName ?? null,
4328
+ toolInput ?? null,
4329
+ toolOutput ?? null
4330
+ );
4331
+ })();
4330
4332
  const existing = cache.get(chatId) ?? [];
4331
4333
  existing.push(
4332
4334
  { role: "user", text: userMessage, timestamp: now },
@@ -6300,6 +6302,68 @@ var init_api_whitelist = __esm({
6300
6302
  }
6301
6303
  });
6302
6304
 
6305
+ // src/search/duckduckgo.ts
6306
+ import { search, SafeSearchType } from "duck-duck-scrape";
6307
+ function getCached(query) {
6308
+ const entry = cache3.get(query.toLowerCase().trim());
6309
+ if (entry && entry.expiry > Date.now()) return entry.results;
6310
+ if (entry) cache3.delete(query.toLowerCase().trim());
6311
+ return null;
6312
+ }
6313
+ function setCache(query, results) {
6314
+ const key = query.toLowerCase().trim();
6315
+ cache3.set(key, { results, expiry: Date.now() + CACHE_TTL_MS });
6316
+ if (cache3.size > 100) {
6317
+ const now = Date.now();
6318
+ for (const [k, v] of cache3) {
6319
+ if (v.expiry < now) cache3.delete(k);
6320
+ }
6321
+ if (cache3.size > 100) {
6322
+ const keys = [...cache3.keys()];
6323
+ for (let i = 0; i < keys.length - 100; i++) {
6324
+ cache3.delete(keys[i]);
6325
+ }
6326
+ }
6327
+ }
6328
+ }
6329
+ async function searchWeb(query, maxResults = 5) {
6330
+ const effectiveMax = Math.min(Math.max(maxResults, 1), 10);
6331
+ const cached = getCached(query);
6332
+ if (cached) {
6333
+ log(`[search] Cache hit for: "${query.slice(0, 50)}"`);
6334
+ return { query, results: cached.slice(0, effectiveMax), cached: true };
6335
+ }
6336
+ log(`[search] Searching DuckDuckGo for: "${query.slice(0, 80)}"`);
6337
+ try {
6338
+ const ddgResults = await search(query, {
6339
+ safeSearch: SafeSearchType.MODERATE
6340
+ });
6341
+ if (ddgResults.noResults || !ddgResults.results?.length) {
6342
+ return { query, results: [], cached: false };
6343
+ }
6344
+ const results = ddgResults.results.slice(0, effectiveMax).map((r) => ({
6345
+ title: r.title,
6346
+ url: r.url,
6347
+ // strip HTML bold tags from description
6348
+ description: r.rawDescription?.replace(/<\/?b>/gi, "") || r.description?.replace(/<\/?b>/gi, "") || ""
6349
+ }));
6350
+ setCache(query, results);
6351
+ return { query, results, cached: false };
6352
+ } catch (err) {
6353
+ warn(`[search] DuckDuckGo search failed: ${err instanceof Error ? err.message : String(err)}`);
6354
+ return { query, results: [], cached: false };
6355
+ }
6356
+ }
6357
+ var CACHE_TTL_MS, cache3;
6358
+ var init_duckduckgo = __esm({
6359
+ "src/search/duckduckgo.ts"() {
6360
+ "use strict";
6361
+ init_log();
6362
+ CACHE_TTL_MS = 5 * 60 * 1e3;
6363
+ cache3 = /* @__PURE__ */ new Map();
6364
+ }
6365
+ });
6366
+
6303
6367
  // src/backends/api-tools.ts
6304
6368
  import { tool } from "ai";
6305
6369
  import { z } from "zod";
@@ -6470,10 +6534,42 @@ function createListFilesTool() {
6470
6534
  }
6471
6535
  });
6472
6536
  }
6537
+ function createWebSearchTool() {
6538
+ return tool({
6539
+ description: "Search the web using DuckDuckGo for current information. Use this when you need up-to-date facts, news, documentation, or answers that may not be in your training data.",
6540
+ inputSchema: z.object({
6541
+ query: z.string().describe("The search query"),
6542
+ maxResults: z.number().optional().describe("Maximum results to return (default: 5, max: 10)")
6543
+ }),
6544
+ execute: async ({ query, maxResults }) => {
6545
+ log(`[api-tools] Web search: ${query.slice(0, 80)}`);
6546
+ try {
6547
+ const response = await searchWeb(query, maxResults ?? 5);
6548
+ if (response.results.length === 0) {
6549
+ return { query, results: [], message: "No results found." };
6550
+ }
6551
+ return {
6552
+ query,
6553
+ resultCount: response.results.length,
6554
+ results: response.results.map((r, i) => ({
6555
+ rank: i + 1,
6556
+ title: r.title,
6557
+ url: r.url,
6558
+ description: r.description
6559
+ }))
6560
+ };
6561
+ } catch (err) {
6562
+ return { error: `Search failed: ${err instanceof Error ? err.message : String(err)}` };
6563
+ }
6564
+ }
6565
+ });
6566
+ }
6473
6567
  function buildApiTools(chatId, permMode, mcpTools) {
6568
+ const webSearch = createWebSearchTool();
6474
6569
  const readOnly = {
6475
6570
  readFile: createReadFileTool(),
6476
- listFiles: createListFilesTool()
6571
+ listFiles: createListFilesTool(),
6572
+ webSearch
6477
6573
  };
6478
6574
  const writable = {
6479
6575
  ...readOnly,
@@ -6513,6 +6609,7 @@ var init_api_tools = __esm({
6513
6609
  init_paths();
6514
6610
  init_api_whitelist();
6515
6611
  init_log();
6612
+ init_duckduckgo();
6516
6613
  execFileAsync2 = promisify2(execFile);
6517
6614
  HOME = homedir3();
6518
6615
  SENSITIVE_PATTERNS = [
@@ -6783,9 +6880,13 @@ var init_api_common = __esm({
6783
6880
  signal,
6784
6881
  messageHistory,
6785
6882
  onToolAction,
6786
- permMode: rawPermMode
6883
+ permMode: rawPermMode,
6884
+ thinkingLevel,
6885
+ onThinking
6787
6886
  } = opts;
6788
6887
  const permMode = rawPermMode ?? "safe";
6888
+ const reasoningEnabled = thinkingLevel ? !["off", "none"].includes(thinkingLevel) : false;
6889
+ const reasoningLevel = reasoningEnabled ? thinkingLevel === "low" ? "low" : thinkingLevel === "medium" ? "medium" : "high" : void 0;
6789
6890
  const apiMessages = messageHistory ?? [
6790
6891
  { role: "user", content: prompt }
6791
6892
  ];
@@ -6815,6 +6916,8 @@ var init_api_common = __esm({
6815
6916
  model: languageModel,
6816
6917
  messages,
6817
6918
  abortSignal,
6919
+ // Reasoning: pass through when enabled (model must support it)
6920
+ ...reasoningLevel ? { reasoning: reasoningLevel } : {},
6818
6921
  // Tool calling: provide tools and allow up to 5 steps
6819
6922
  ...hasTools ? {
6820
6923
  tools: tools2,
@@ -6842,6 +6945,9 @@ var init_api_common = __esm({
6842
6945
  if (chunk.type === "text-delta" && onStream) {
6843
6946
  onStream(chunk.text);
6844
6947
  }
6948
+ if (chunk.type === "reasoning-delta" && onThinking) {
6949
+ onThinking(chunk.text);
6950
+ }
6845
6951
  }
6846
6952
  });
6847
6953
  let fullText = "";
@@ -8021,7 +8127,7 @@ var init_ollama2 = __esm({
8021
8127
  const result = await this.streamDirectWithHistory(
8022
8128
  cleanPrompt,
8023
8129
  model2,
8024
- "ollama",
8130
+ opts?.chatId ?? "ollama",
8025
8131
  apiOpts
8026
8132
  );
8027
8133
  return {
@@ -8123,18 +8229,29 @@ var init_openrouter = __esm({
8123
8229
  /**
8124
8230
  * Execute a prompt via OpenRouter's REST API.
8125
8231
  * Delegates to ApiBackendBase.streamDirectWithHistory() for streaming + history.
8126
- * Maps ApiStreamDirectResult (Vercel AI SDK token names) to StreamDirectResult (CC-Claw standard).
8232
+ * Maps StreamDirectOpts (CC-Claw standard) ApiStreamDirectOpts (Vercel AI SDK layer),
8233
+ * including tool actions, permission mode, thinking tokens, and conversation history.
8127
8234
  */
8128
8235
  async streamDirect(prompt, model2, opts) {
8236
+ const onToolAction = opts?.onToolAction ? (event) => {
8237
+ const input = event.input ?? {};
8238
+ const result2 = event.type === "tool_end" ? String(event.output ?? "") : void 0;
8239
+ opts.onToolAction(event.toolName, input, result2);
8240
+ } : void 0;
8129
8241
  const apiOpts = {
8130
8242
  timeoutMs: opts?.timeoutMs,
8131
8243
  onStream: opts?.onStream,
8132
- signal: opts?.signal
8244
+ signal: opts?.signal,
8245
+ messageHistory: opts?.messageHistory,
8246
+ onToolAction,
8247
+ permMode: opts?.permMode,
8248
+ thinkingLevel: opts?.thinkingLevel,
8249
+ onThinking: opts?.onThinking
8133
8250
  };
8134
8251
  const result = await this.streamDirectWithHistory(
8135
8252
  prompt,
8136
8253
  model2,
8137
- "openrouter",
8254
+ opts?.chatId ?? "openrouter",
8138
8255
  apiOpts
8139
8256
  );
8140
8257
  return {
@@ -8495,7 +8612,14 @@ function buildContextBridge(chatId, pairs = 15) {
8495
8612
  const backendLabel = row.backend ?? "unknown";
8496
8613
  const date = row.created_at.slice(0, 10);
8497
8614
  const roleLabel = row.role === "user" ? "You" : "Assistant";
8498
- const line = `[${backendLabel} \xB7 ${date}] ${roleLabel}: ${row.content}`;
8615
+ let line = `[${backendLabel} \xB7 ${date}] ${roleLabel}: ${row.content}`;
8616
+ if (row.tool_name) {
8617
+ const toolSummary = formatToolSummary(row.tool_name, row.tool_input, row.tool_output);
8618
+ if (toolSummary) {
8619
+ line += `
8620
+ ${toolSummary}`;
8621
+ }
8622
+ }
8499
8623
  if (totalChars + line.length > MAX_BRIDGE_CHARS) break;
8500
8624
  lines.push(line);
8501
8625
  totalChars += line.length;
@@ -8503,6 +8627,18 @@ function buildContextBridge(chatId, pairs = 15) {
8503
8627
  lines.push(`[End of recent history \u2014 you are continuing this conversation]`);
8504
8628
  return lines.join("\n");
8505
8629
  }
8630
+ function formatToolSummary(toolName, toolInput, toolOutput) {
8631
+ const parts = [`\u{1F527} Tool: ${toolName}`];
8632
+ if (toolInput) {
8633
+ const truncInput = toolInput.length > TOOL_SUMMARY_MAX_CHARS ? toolInput.slice(0, TOOL_SUMMARY_MAX_CHARS) + "\u2026" : toolInput;
8634
+ parts.push(`Input: ${truncInput}`);
8635
+ }
8636
+ if (toolOutput) {
8637
+ const truncOutput = toolOutput.length > TOOL_SUMMARY_MAX_CHARS ? toolOutput.slice(0, TOOL_SUMMARY_MAX_CHARS) + "\u2026" : toolOutput;
8638
+ parts.push(`Result: ${truncOutput}`);
8639
+ }
8640
+ return parts.join(" | ");
8641
+ }
8506
8642
  async function injectMemoryContext(userMessage, chatId) {
8507
8643
  const provider = resolveProvider();
8508
8644
  const useVectors = !!provider;
@@ -8597,7 +8733,7 @@ async function injectMemoryContext(userMessage, chatId) {
8597
8733
  ${lines.join("\n")}
8598
8734
  [End memory context]`;
8599
8735
  }
8600
- var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, BASE_VECTOR_TOP_K, BASE_FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS, MAX_BRIDGE_CHARS, pendingContextBridges;
8736
+ var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, BASE_VECTOR_TOP_K, BASE_FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS, MAX_BRIDGE_CHARS, pendingContextBridges, TOOL_SUMMARY_MAX_CHARS;
8601
8737
  var init_inject = __esm({
8602
8738
  "src/memory/inject.ts"() {
8603
8739
  "use strict";
@@ -8613,6 +8749,7 @@ var init_inject = __esm({
8613
8749
  MAX_SESSION_CHARS = 800;
8614
8750
  MAX_BRIDGE_CHARS = 48e3;
8615
8751
  pendingContextBridges = /* @__PURE__ */ new Map();
8752
+ TOOL_SUMMARY_MAX_CHARS = 200;
8616
8753
  }
8617
8754
  });
8618
8755
 
@@ -10945,7 +11082,7 @@ function invalidateSkillCache() {
10945
11082
  }
10946
11083
  async function discoverAllSkills() {
10947
11084
  const now = Date.now();
10948
- if (cachedSkills !== null && now - cacheTimestamp < CACHE_TTL_MS) {
11085
+ if (cachedSkills !== null && now - cacheTimestamp < CACHE_TTL_MS2) {
10949
11086
  return cachedSkills;
10950
11087
  }
10951
11088
  if (pendingScan !== null) {
@@ -11129,7 +11266,7 @@ function resolveSkillForTask(skillName) {
11129
11266
  }
11130
11267
  return null;
11131
11268
  }
11132
- var SKILL_FILE_CANDIDATES, BACKEND_SKILL_DIRS, CACHE_TTL_MS, cachedSkills, cacheTimestamp, pendingScan;
11269
+ var SKILL_FILE_CANDIDATES, BACKEND_SKILL_DIRS, CACHE_TTL_MS2, cachedSkills, cacheTimestamp, pendingScan;
11133
11270
  var init_discover = __esm({
11134
11271
  "src/skills/discover.ts"() {
11135
11272
  "use strict";
@@ -11149,7 +11286,7 @@ var init_discover = __esm({
11149
11286
  join11(homedir4(), ".cursor", "skills-cursor")
11150
11287
  ]
11151
11288
  };
11152
- CACHE_TTL_MS = 3e5;
11289
+ CACHE_TTL_MS2 = 3e5;
11153
11290
  cachedSkills = null;
11154
11291
  cacheTimestamp = 0;
11155
11292
  pendingScan = null;
@@ -11664,14 +11801,14 @@ function scanTemplates() {
11664
11801
  }
11665
11802
  }
11666
11803
  function getTemplate(name) {
11667
- if (Date.now() - lastScanMs > CACHE_TTL_MS2) scanTemplates();
11804
+ if (Date.now() - lastScanMs > CACHE_TTL_MS3) scanTemplates();
11668
11805
  return templateCache.get(name) ?? null;
11669
11806
  }
11670
11807
  function listTemplates() {
11671
- if (Date.now() - lastScanMs > CACHE_TTL_MS2) scanTemplates();
11808
+ if (Date.now() - lastScanMs > CACHE_TTL_MS3) scanTemplates();
11672
11809
  return Array.from(templateCache.values());
11673
11810
  }
11674
- var templateCache, lastScanMs, CACHE_TTL_MS2;
11811
+ var templateCache, lastScanMs, CACHE_TTL_MS3;
11675
11812
  var init_loader2 = __esm({
11676
11813
  "src/agents/templates/loader.ts"() {
11677
11814
  "use strict";
@@ -11679,7 +11816,7 @@ var init_loader2 = __esm({
11679
11816
  init_log();
11680
11817
  templateCache = /* @__PURE__ */ new Map();
11681
11818
  lastScanMs = 0;
11682
- CACHE_TTL_MS2 = 3e4;
11819
+ CACHE_TTL_MS3 = 3e4;
11683
11820
  }
11684
11821
  });
11685
11822
 
@@ -16496,7 +16633,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
16496
16633
  signal: abortController.signal,
16497
16634
  messageHistory,
16498
16635
  onToolAction,
16499
- permMode: mode
16636
+ permMode: mode,
16637
+ thinkingLevel,
16638
+ onThinking,
16639
+ chatId
16500
16640
  });
16501
16641
  if (!isSyntheticChatId(chatId)) {
16502
16642
  appendToLog(chatId, userMessage, sdResult.text, adapter.id, resolvedModel2, null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.24.2",
3
+ "version": "0.25.0",
4
4
  "description": "CC-Claw: Personal AI assistant on Telegram — multi-backend (Claude, Gemini, Codex, Cursor), sub-agent orchestration, MCP management",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -47,6 +47,7 @@
47
47
  "commander": "^14.0.3",
48
48
  "croner": "^9.0.0",
49
49
  "dotenv": "^16.4.7",
50
+ "duck-duck-scrape": "^2.2.7",
50
51
  "grammy": "^1.35.0",
51
52
  "js-tiktoken": "^1.0.21",
52
53
  "marked": "^9.1.6",
@@ -60,7 +61,6 @@
60
61
  },
61
62
  "devDependencies": {
62
63
  "@types/better-sqlite3": "^7.6.13",
63
- "@types/bun": "^1.3.10",
64
64
  "@types/marked-terminal": "^6.1.1",
65
65
  "@types/node": "^25.3.5",
66
66
  "@vitest/coverage-v8": "^4.0.18",