cc-claw 0.20.0 → 0.20.2

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 +124 -14
  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.0" : (() => {
36
+ VERSION = true ? "0.20.2" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -1774,6 +1774,12 @@ function initSchema(db3) {
1774
1774
  value INTEGER NOT NULL DEFAULT 0
1775
1775
  );
1776
1776
  `);
1777
+ db3.exec(`
1778
+ CREATE TABLE IF NOT EXISTS chat_skill_suggestions (
1779
+ chat_id TEXT PRIMARY KEY,
1780
+ value INTEGER NOT NULL DEFAULT 1
1781
+ );
1782
+ `);
1777
1783
  db3.exec(`
1778
1784
  CREATE TABLE IF NOT EXISTS chat_session_log (
1779
1785
  chat_id TEXT PRIMARY KEY,
@@ -2543,6 +2549,7 @@ __export(chat_settings_exports, {
2543
2549
  getRecentBookmarks: () => getRecentBookmarks,
2544
2550
  getSessionLogEnabled: () => getSessionLogEnabled,
2545
2551
  getShowThinkingUi: () => getShowThinkingUi,
2552
+ getSkillSuggestionsEnabled: () => getSkillSuggestionsEnabled,
2546
2553
  getSummarizer: () => getSummarizer,
2547
2554
  getThinkingLevel: () => getThinkingLevel,
2548
2555
  getToolsMap: () => getToolsMap,
@@ -2559,6 +2566,7 @@ __export(chat_settings_exports, {
2559
2566
  setModel: () => setModel,
2560
2567
  setSessionLogEnabled: () => setSessionLogEnabled,
2561
2568
  setShowThinkingUi: () => setShowThinkingUi,
2569
+ setSkillSuggestionsEnabled: () => setSkillSuggestionsEnabled,
2562
2570
  setSummarizer: () => setSummarizer,
2563
2571
  setThinkingLevel: () => setThinkingLevel,
2564
2572
  setVerboseLevel: () => setVerboseLevel,
@@ -2686,6 +2694,19 @@ function toggleShowThinkingUi(chatId) {
2686
2694
  setShowThinkingUi(chatId, next);
2687
2695
  return next;
2688
2696
  }
2697
+ function getSkillSuggestionsEnabled(chatId) {
2698
+ const row = getDb().prepare(
2699
+ "SELECT value FROM chat_skill_suggestions WHERE chat_id = ?"
2700
+ ).get(chatId);
2701
+ return (row?.value ?? 1) === 1;
2702
+ }
2703
+ function setSkillSuggestionsEnabled(chatId, enabled) {
2704
+ getDb().prepare(`
2705
+ INSERT INTO chat_skill_suggestions (chat_id, value)
2706
+ VALUES (?, ?)
2707
+ ON CONFLICT(chat_id) DO UPDATE SET value = ?
2708
+ `).run(chatId, enabled ? 1 : 0, enabled ? 1 : 0);
2709
+ }
2689
2710
  function getMode(chatId) {
2690
2711
  const row = getDb().prepare(
2691
2712
  "SELECT mode FROM chat_mode WHERE chat_id = ?"
@@ -3897,6 +3918,7 @@ __export(store_exports5, {
3897
3918
  getSessionSummaries: () => getSessionSummaries,
3898
3919
  getSessionSummariesWithoutEmbeddings: () => getSessionSummariesWithoutEmbeddings,
3899
3920
  getShowThinkingUi: () => getShowThinkingUi,
3921
+ getSkillSuggestionsEnabled: () => getSkillSuggestionsEnabled,
3900
3922
  getSummarizer: () => getSummarizer,
3901
3923
  getThinkingLevel: () => getThinkingLevel,
3902
3924
  getToolsMap: () => getToolsMap,
@@ -3965,6 +3987,7 @@ __export(store_exports5, {
3965
3987
  setSessionLogEnabled: () => setSessionLogEnabled,
3966
3988
  setSessionStartedAt: () => setSessionStartedAt,
3967
3989
  setShowThinkingUi: () => setShowThinkingUi,
3990
+ setSkillSuggestionsEnabled: () => setSkillSuggestionsEnabled,
3968
3991
  setSummarizer: () => setSummarizer,
3969
3992
  setThinkingLevel: () => setThinkingLevel,
3970
3993
  setVerboseLevel: () => setVerboseLevel,
@@ -7102,7 +7125,7 @@ function searchContext(userMessage) {
7102
7125
  }
7103
7126
  return null;
7104
7127
  }
7105
- async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permMode, responseStyle, agentMode, sideQuestContext, planningDirective) {
7128
+ async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permMode, responseStyle, agentMode, sideQuestContext, planningDirective, chatContext) {
7106
7129
  const sections = [];
7107
7130
  if (planningDirective) {
7108
7131
  sections.push(planningDirective);
@@ -7121,6 +7144,19 @@ async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permM
7121
7144
  sections.push("[Response Style]\nYou should be detailed and thorough in your responses. Explain concepts fully and provide comprehensive answers.");
7122
7145
  }
7123
7146
  }
7147
+ if (chatId && tier !== "slim") {
7148
+ const parts = [`Chat ID: ${chatId}`];
7149
+ if (chatContext?.chatTitle) parts.push(`Group: ${chatContext.chatTitle}`);
7150
+ if (chatContext?.threadId) parts.push(`Forum topic thread: ${chatContext.threadId}`);
7151
+ try {
7152
+ const { getAllChatAliases: getAllChatAliases3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
7153
+ const match = getAllChatAliases3().find((a) => a.chatId === chatId);
7154
+ if (match) parts.push(`Alias: ${match.alias}`);
7155
+ } catch {
7156
+ }
7157
+ sections.push(`[Current chat]
7158
+ ${parts.join("\n")}`);
7159
+ }
7124
7160
  if (tier === "full") {
7125
7161
  const ctx = searchContext(userMessage);
7126
7162
  if (ctx) {
@@ -13578,7 +13614,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
13578
13614
  const tier = bootstrapTier ?? "full";
13579
13615
  const effectiveAgentMode = optsAgentMode ?? getAgentMode(settingsChat);
13580
13616
  const sideQuestCtx = settingsSourceChatId ? { parentChatId: settingsSourceChatId, actualChatId: chatId } : void 0;
13581
- const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx, planningDirective);
13617
+ const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx, planningDirective, opts?.chatContext);
13582
13618
  if (adapter.streamDirect) {
13583
13619
  const resolvedModel2 = model2 ?? adapter.defaultModel;
13584
13620
  const abortController = new AbortController();
@@ -18531,6 +18567,13 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
18531
18567
  if (safePage < totalPages) navRow.push({ label: `Page ${safePage + 1} \u2192`, data: `skills:page:${safePage + 1}` });
18532
18568
  buttons.push(navRow);
18533
18569
  }
18570
+ const { getSkillSuggestionsEnabled: getSkillSuggestionsEnabled2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
18571
+ const extractEnabled = getSkillSuggestionsEnabled2(chatId);
18572
+ buttons.push([{
18573
+ label: `${extractEnabled ? "\u2713" : "\u25CB"} Auto-Extract Skills`,
18574
+ data: "skills:toggle-extract",
18575
+ ...extractEnabled ? { style: "primary" } : {}
18576
+ }]);
18534
18577
  const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
18535
18578
  await sendOrEditKeyboard(chatId, channel, messageId, header2, buttons);
18536
18579
  }
@@ -22535,10 +22578,12 @@ import { join as join25 } from "path";
22535
22578
  import { writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
22536
22579
  function isSkillWorthy(signals) {
22537
22580
  const { toolUseCount, tokenOutput, elapsedMs, userMessage } = signals;
22538
- if (toolUseCount < 3) return false;
22581
+ if (toolUseCount < 8) return false;
22539
22582
  const supplementary = [
22540
- tokenOutput >= 1500,
22541
- elapsedMs >= 2e4
22583
+ tokenOutput >= 3e3,
22584
+ // substantial output
22585
+ elapsedMs >= 45e3
22586
+ // took real effort (45s+)
22542
22587
  ].filter(Boolean).length;
22543
22588
  if (supplementary < 1) return false;
22544
22589
  const words = userMessage.split(/\s+/).length;
@@ -23652,6 +23697,12 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
23652
23697
  Example: /limits ${bid} daily 500000`, { parseMode: "plain" });
23653
23698
  }
23654
23699
  return;
23700
+ } else if (data === "skills:toggle-extract") {
23701
+ const { getSkillSuggestionsEnabled: getSkillSuggestionsEnabled2, setSkillSuggestionsEnabled: setSkillSuggestionsEnabled2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
23702
+ const current = getSkillSuggestionsEnabled2(chatId);
23703
+ setSkillSuggestionsEnabled2(chatId, !current);
23704
+ const skills2 = await discoverAllSkills();
23705
+ await sendSkillsPage(chatId, channel, skills2, 1, messageId);
23655
23706
  } else if (data.startsWith("skills:page:")) {
23656
23707
  const page = parseInt(data.slice(12), 10);
23657
23708
  const skills2 = await discoverAllSkills();
@@ -23675,12 +23726,42 @@ Example: /limits ${bid} daily 500000`, { parseMode: "plain" });
23675
23726
  });
23676
23727
  const parsed = parseExtractedSkill2(extractionResponse.text);
23677
23728
  if (parsed) {
23678
- const { path } = await saveSkill2(parsed.name, parsed.content);
23679
- clearPendingDraft2(chatId);
23680
- await channel.sendText(chatId, `\u2705 Skill "${parsed.name}" saved.
23681
- Path: ${path}
23729
+ const { storePendingDraft: updateDraft } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
23730
+ updateDraft(chatId, { ...draft, name: parsed.name, content: parsed.content });
23731
+ const escaped = parsed.content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
23732
+ const header2 = `\u{1F4CB} <b>Skill Preview: "${parsed.name}"</b>
23682
23733
 
23683
- Use /skills to see all available skills.`, { parseMode: "plain" });
23734
+ `;
23735
+ const MAX_PREVIEW = 3500 - header2.length;
23736
+ if (typeof channel.sendKeyboard === "function") {
23737
+ if (escaped.length > MAX_PREVIEW) {
23738
+ await channel.sendText(chatId, `${header2}<pre>${escaped.slice(0, 3500)}</pre>
23739
+
23740
+ \u2026(truncated \u2014 full skill will be saved)`, { parseMode: "html" });
23741
+ await channel.sendKeyboard(
23742
+ chatId,
23743
+ `Save "${parsed.name}" as a reusable skill?`,
23744
+ [[
23745
+ { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
23746
+ { label: "\u2715 Discard", data: "skill:discard" }
23747
+ ]]
23748
+ );
23749
+ } else {
23750
+ await channel.sendKeyboard(
23751
+ chatId,
23752
+ `${header2}<pre>${escaped}</pre>`,
23753
+ [[
23754
+ { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
23755
+ { label: "\u2715 Discard", data: "skill:discard" }
23756
+ ]]
23757
+ );
23758
+ }
23759
+ } else {
23760
+ const { path } = await saveSkill2(parsed.name, parsed.content);
23761
+ clearPendingDraft2(chatId);
23762
+ await channel.sendText(chatId, `\u2705 Skill "${parsed.name}" saved.
23763
+ Path: ${path}`, { parseMode: "plain" });
23764
+ }
23684
23765
  } else {
23685
23766
  clearPendingDraft2(chatId);
23686
23767
  await channel.sendText(chatId, "Could not extract a well-formed skill from this conversation. Try /remember to save key steps manually.", { parseMode: "plain" });
@@ -23690,10 +23771,32 @@ Use /skills to see all available skills.`, { parseMode: "plain" });
23690
23771
  await channel.sendText(chatId, `Skill extraction failed: ${e.message}`, { parseMode: "plain" });
23691
23772
  }
23692
23773
  return;
23774
+ } else if (data === "skill:confirm-save") {
23775
+ const { getPendingDraft: getPendingDraft2, clearPendingDraft: clearPendingDraft2, saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
23776
+ const draft = getPendingDraft2(chatId);
23777
+ if (!draft || !draft.name || !draft.content) {
23778
+ await channel.sendText(chatId, "No pending skill to save.", { parseMode: "plain" });
23779
+ return;
23780
+ }
23781
+ try {
23782
+ const { path } = await saveSkill2(draft.name, draft.content);
23783
+ clearPendingDraft2(chatId);
23784
+ if (messageId) await replaceWithText(`\u2705 Skill "${draft.name}" saved.
23785
+ Path: ${path}
23786
+
23787
+ Use /skills to see all available skills.`);
23788
+ else await channel.sendText(chatId, `\u2705 Skill "${draft.name}" saved.
23789
+ Path: ${path}
23790
+
23791
+ Use /skills to see all available skills.`, { parseMode: "plain" });
23792
+ } catch (e) {
23793
+ await channel.sendText(chatId, `Failed to save skill: ${e.message}`, { parseMode: "plain" });
23794
+ }
23795
+ return;
23693
23796
  } else if (data === "skill:discard") {
23694
23797
  const { clearPendingDraft: clearPendingDraft2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
23695
23798
  clearPendingDraft2(chatId);
23696
- await channel.sendText(chatId, "Skill draft discarded.", { parseMode: "plain" });
23799
+ if (messageId) await replaceWithText("\u{1F44C} Noted.");
23697
23800
  return;
23698
23801
  } else if (data.startsWith("skill:")) {
23699
23802
  const parts = data.slice(6).split(":");
@@ -24597,6 +24700,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
24597
24700
  maxTurns,
24598
24701
  agentMode: effectiveAgentMode,
24599
24702
  ...effectiveThinking ? { thinkingLevel: effectiveThinking } : {},
24703
+ chatContext: { chatTitle: msg.chatTitle, threadId: msg.threadId },
24600
24704
  onThinking: liveStatus || sessionLog ? (chunk) => {
24601
24705
  if (liveStatus) liveStatus.addThinking(chunk);
24602
24706
  if (sessionLog) sessionLog.logThinking(chunk);
@@ -24702,7 +24806,8 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
24702
24806
  log(`[reflection] Signal detection error: ${e}`);
24703
24807
  }
24704
24808
  try {
24705
- if (intent === "agentic" && toolUseCount > 0) {
24809
+ const { getSkillSuggestionsEnabled: getSkillSuggestionsEnabled2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
24810
+ if (intent === "agentic" && toolUseCount > 0 && getSkillSuggestionsEnabled2(chatId)) {
24706
24811
  const { isSkillWorthy: isSkillWorthy2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
24707
24812
  const signals = {
24708
24813
  toolUseCount,
@@ -24712,6 +24817,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
24712
24817
  };
24713
24818
  if (isSkillWorthy2(signals) && typeof channel.sendKeyboard === "function") {
24714
24819
  const { storePendingDraft: storePendingDraft2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
24820
+ const taskSummary = (cleanText || text).slice(0, 120) + ((cleanText || text).length > 120 ? "\u2026" : "");
24715
24821
  storePendingDraft2(chatId, {
24716
24822
  name: "",
24717
24823
  content: "",
@@ -24720,7 +24826,11 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
24720
24826
  });
24721
24827
  await channel.sendKeyboard(
24722
24828
  chatId,
24723
- "\u{1F4A1} That looked like a reusable workflow. Want me to extract it as a skill?",
24829
+ `\u{1F4A1} That looked like a reusable workflow:
24830
+
24831
+ "${taskSummary}"
24832
+
24833
+ Want me to extract it as a reusable skill? (${toolUseCount} tools used, ${elapsedSec}s)`,
24724
24834
  [[
24725
24835
  { label: "\u2705 Extract Skill", data: "skill:extract", style: "success" },
24726
24836
  { label: "\u2715 No thanks", data: "skill:discard" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
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",