cc-claw 0.11.3 → 0.12.1

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 +341 -238
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -72,7 +72,7 @@ var VERSION;
72
72
  var init_version = __esm({
73
73
  "src/version.ts"() {
74
74
  "use strict";
75
- VERSION = true ? "0.11.3" : (() => {
75
+ VERSION = true ? "0.12.1" : (() => {
76
76
  try {
77
77
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
78
78
  } catch {
@@ -682,18 +682,15 @@ __export(store_exports4, {
682
682
  cleanupReflectionData: () => cleanupReflectionData,
683
683
  getAppliedInsightCountToday: () => getAppliedInsightCountToday,
684
684
  getAppliedInsights: () => getAppliedInsights,
685
- getConflictingInsights: () => getConflictingInsights,
686
685
  getGrowthMetrics: () => getGrowthMetrics,
687
686
  getInsightById: () => getInsightById,
688
687
  getLastAnalysisTime: () => getLastAnalysisTime,
689
688
  getPendingInsightCount: () => getPendingInsightCount,
690
689
  getPendingInsights: () => getPendingInsights,
691
- getRecommendations: () => getRecommendations,
692
690
  getReflectionModelConfig: () => getReflectionModelConfig,
693
691
  getReflectionStatus: () => getReflectionStatus,
694
692
  getRejectedInsights: () => getRejectedInsights,
695
693
  getSignalCountForChat: () => getSignalCountForChat,
696
- getSignalsForPeriod: () => getSignalsForPeriod,
697
694
  getUnprocessedSignalCount: () => getUnprocessedSignalCount,
698
695
  getUnprocessedSignals: () => getUnprocessedSignals,
699
696
  initReflectionTables: () => initReflectionTables,
@@ -839,13 +836,6 @@ function getSignalCountForChat(db3, chatId, minutesBack) {
839
836
  `).get(chatId, minutesBack);
840
837
  return row.count;
841
838
  }
842
- function getSignalsForPeriod(db3, chatId, period) {
843
- return db3.prepare(`
844
- SELECT * FROM feedback_signals
845
- WHERE chatId = ? AND date(created_at) = ?
846
- ORDER BY created_at ASC
847
- `).all(chatId, period);
848
- }
849
839
  function getUnprocessedSignalCount(db3, chatId) {
850
840
  const row = db3.prepare(`
851
841
  SELECT COUNT(*) as count FROM feedback_signals
@@ -916,14 +906,6 @@ function getRejectedInsights(db3, chatId, limit = 20) {
916
906
  LIMIT ?
917
907
  `).all(chatId, limit);
918
908
  }
919
- function getRecommendations(db3, chatId, limit = 20) {
920
- return db3.prepare(`
921
- SELECT * FROM insights
922
- WHERE chatId = ? AND status = 'recommendation'
923
- ORDER BY created_at DESC
924
- LIMIT ?
925
- `).all(chatId, limit);
926
- }
927
909
  function updateInsightStatus(db3, id, status) {
928
910
  if (status === "applied") {
929
911
  db3.prepare(`
@@ -944,13 +926,6 @@ function updateInsightRollback(db3, id, rollbackData) {
944
926
  function updateInsightEffectiveness(db3, id, score) {
945
927
  db3.prepare("UPDATE insights SET effectiveness = ? WHERE id = ?").run(score, id);
946
928
  }
947
- function getConflictingInsights(db3, chatId, targetFile) {
948
- return db3.prepare(`
949
- SELECT * FROM insights
950
- WHERE chatId = ? AND targetFile = ? AND status = 'applied'
951
- ORDER BY appliedAt DESC
952
- `).all(chatId, targetFile);
953
- }
954
929
  function getPendingInsightCount(db3, chatId) {
955
930
  const row = db3.prepare(`
956
931
  SELECT COUNT(*) as count FROM insights WHERE chatId = ? AND status = 'pending'
@@ -960,7 +935,7 @@ function getPendingInsightCount(db3, chatId) {
960
935
  function getAppliedInsightCountToday(db3, chatId) {
961
936
  const row = db3.prepare(`
962
937
  SELECT COUNT(*) as count FROM insights
963
- WHERE chatId = ? AND status = 'applied' AND date(appliedAt) = date('now')
938
+ WHERE chatId = ? AND status = 'applied' AND appliedAt >= date('now') AND appliedAt < date('now', '+1 day')
964
939
  `).get(chatId);
965
940
  return row.count;
966
941
  }
@@ -1558,6 +1533,7 @@ function initDatabase() {
1558
1533
  target TEXT,
1559
1534
  delivery_mode TEXT NOT NULL DEFAULT 'announce',
1560
1535
  timezone TEXT NOT NULL DEFAULT 'UTC',
1536
+ job_type TEXT DEFAULT 'normal',
1561
1537
  enabled INTEGER NOT NULL DEFAULT 1,
1562
1538
  active INTEGER NOT NULL DEFAULT 1,
1563
1539
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
@@ -1594,6 +1570,7 @@ function initDatabase() {
1594
1570
  target TEXT,
1595
1571
  delivery_mode TEXT NOT NULL DEFAULT 'announce',
1596
1572
  timezone TEXT NOT NULL DEFAULT 'UTC',
1573
+ job_type TEXT DEFAULT 'normal',
1597
1574
  enabled INTEGER NOT NULL DEFAULT 1,
1598
1575
  active INTEGER NOT NULL DEFAULT 1,
1599
1576
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
@@ -1681,6 +1658,14 @@ function initDatabase() {
1681
1658
  db.exec("ALTER TABLE jobs ADD COLUMN title TEXT");
1682
1659
  } catch {
1683
1660
  }
1661
+ try {
1662
+ db.exec("ALTER TABLE jobs ADD COLUMN job_type TEXT DEFAULT 'normal'");
1663
+ } catch {
1664
+ }
1665
+ try {
1666
+ db.exec("UPDATE jobs SET job_type = 'reflection' WHERE description LIKE '%reflection analysis%' AND job_type = 'normal'");
1667
+ } catch {
1668
+ }
1684
1669
  backfillJobTitles(db);
1685
1670
  }
1686
1671
  } catch {
@@ -2529,8 +2514,8 @@ function getBackendUsageInWindow(backend2, windowType) {
2529
2514
  function insertJob(params) {
2530
2515
  const result = db.prepare(`
2531
2516
  INSERT INTO jobs (schedule_type, cron, at_time, every_ms, title, description, chat_id,
2532
- backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone)
2533
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2517
+ backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone, job_type)
2518
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2534
2519
  `).run(
2535
2520
  params.scheduleType,
2536
2521
  params.cron ?? null,
@@ -2548,7 +2533,8 @@ function insertJob(params) {
2548
2533
  params.channel ?? null,
2549
2534
  params.target ?? null,
2550
2535
  params.deliveryMode ?? "announce",
2551
- params.timezone ?? "UTC"
2536
+ params.timezone ?? "UTC",
2537
+ params.jobType ?? "normal"
2552
2538
  );
2553
2539
  return getJobById(Number(result.lastInsertRowid));
2554
2540
  }
@@ -2629,7 +2615,8 @@ function updateJob(id, fields) {
2629
2615
  channel: "channel",
2630
2616
  target: "target",
2631
2617
  deliveryMode: "delivery_mode",
2632
- timezone: "timezone"
2618
+ timezone: "timezone",
2619
+ jobType: "job_type"
2633
2620
  };
2634
2621
  for (const [key, val] of Object.entries(fields)) {
2635
2622
  const col = fieldMap[key];
@@ -3066,7 +3053,7 @@ var init_store5 = __esm({
3066
3053
  SELECT id, schedule_type as scheduleType, cron, at_time as atTime, every_ms as everyMs,
3067
3054
  title, description, chat_id as chatId, backend, model, thinking, timeout, fallbacks,
3068
3055
  session_type as sessionType, channel, target, delivery_mode as deliveryMode,
3069
- timezone, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
3056
+ timezone, job_type as jobType, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
3070
3057
  next_run_at as nextRunAt, consecutive_failures as consecutiveFailures
3071
3058
  FROM jobs
3072
3059
  `;
@@ -4958,7 +4945,7 @@ ${transcript}`;
4958
4945
  if (!resultText) resultText = accumulatedText;
4959
4946
  if (!resultText) {
4960
4947
  warn(`[summarize] ${adapter.id}:${model2} returned empty result for chat ${chatId}`);
4961
- return false;
4948
+ return { success: false, rawText: "" };
4962
4949
  }
4963
4950
  const summaryMatch = resultText.match(/SUMMARY:\s*(.+?)(?=\nKEY_DETAILS:|\nTOPICS:|$)/s);
4964
4951
  const keyDetailsMatch = resultText.match(/KEY_DETAILS:\s*(.+?)(?=\nTOPICS:|$)/s);
@@ -4970,16 +4957,44 @@ Key details: ${keyDetails}`;
4970
4957
  const topics = topicsMatch?.[1]?.trim() ?? "";
4971
4958
  saveSessionSummaryWithEmbedding(chatId, summary, topics, Math.floor(entries.length / 2));
4972
4959
  log(`[summarize] Saved summary via ${adapter.id}:${model2} for chat ${chatId}`);
4973
- return true;
4960
+ return { success: true, rawText: resultText };
4974
4961
  } catch (err) {
4975
4962
  warn(`[summarize] ${adapter.id}:${model2} failed for ${chatId}: ${errorMessage(err)}`);
4976
- return false;
4963
+ return { success: false, rawText: "" };
4977
4964
  }
4978
4965
  }
4979
- async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend) {
4966
+ async function extractAndLogSignals(rawText, chatId, adapterId, model2) {
4967
+ try {
4968
+ const { getReflectionStatus: getReflectionStatus2, logSignal: logSignal2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
4969
+ const { isSyntheticChatId: isSyntheticChatId3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
4970
+ const db3 = getDb();
4971
+ if (isSyntheticChatId3(chatId) || getReflectionStatus2(db3, chatId) === "frozen") return;
4972
+ const signalMatches = rawText.matchAll(/^-\s*\[(\w+)\]\s*(.+)/gm);
4973
+ for (const match of signalMatches) {
4974
+ const signalType = match[1].toLowerCase();
4975
+ const trigger = match[2].trim();
4976
+ if (VALID_SIGNAL_TYPES.includes(signalType)) {
4977
+ logSignal2(db3, {
4978
+ chatId,
4979
+ signalType,
4980
+ trigger,
4981
+ context: null,
4982
+ source: "llm_summary",
4983
+ confidence: 0.7,
4984
+ backend: adapterId,
4985
+ model: model2,
4986
+ thinkingLevel: null
4987
+ });
4988
+ }
4989
+ }
4990
+ } catch (e) {
4991
+ warn(`[summarize] Signal extraction failed for ${chatId}: ${e}`);
4992
+ }
4993
+ }
4994
+ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend, clearLogAfter = true) {
4980
4995
  const pairCount = getMessagePairCount(chatId);
4981
4996
  if (pairCount < MIN_PAIRS) {
4982
- clearLog(chatId);
4997
+ if (clearLogAfter) clearLog(chatId);
4983
4998
  return false;
4984
4999
  }
4985
5000
  const entries = getLog(chatId);
@@ -4996,8 +5011,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
4996
5011
  const model2 = config2.model ?? adapter.summarizerModel;
4997
5012
  const key = `${adapter.id}:${model2}`;
4998
5013
  tried.add(key);
4999
- if (await attemptSummarize(chatId, adapter, model2, entries)) {
5000
- clearLog(chatId);
5014
+ const result = await attemptSummarize(chatId, adapter, model2, entries);
5015
+ if (result.success) {
5016
+ await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
5017
+ if (clearLogAfter) clearLog(chatId);
5001
5018
  return true;
5002
5019
  }
5003
5020
  }
@@ -5010,8 +5027,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
5010
5027
  const key = `${targetAdapter.id}:${model2}`;
5011
5028
  if (!tried.has(key)) {
5012
5029
  tried.add(key);
5013
- if (await attemptSummarize(chatId, targetAdapter, model2, entries)) {
5014
- clearLog(chatId);
5030
+ const result = await attemptSummarize(chatId, targetAdapter, model2, entries);
5031
+ if (result.success) {
5032
+ await extractAndLogSignals(result.rawText, chatId, targetAdapter.id, model2);
5033
+ if (clearLogAfter) clearLog(chatId);
5015
5034
  return true;
5016
5035
  }
5017
5036
  }
@@ -5023,8 +5042,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
5023
5042
  const key = `${adapter.id}:${model2}`;
5024
5043
  if (!tried.has(key)) {
5025
5044
  tried.add(key);
5026
- if (await attemptSummarize(chatId, adapter, model2, entries)) {
5027
- clearLog(chatId);
5045
+ const result = await attemptSummarize(chatId, adapter, model2, entries);
5046
+ if (result.success) {
5047
+ await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
5048
+ if (clearLogAfter) clearLog(chatId);
5028
5049
  return true;
5029
5050
  }
5030
5051
  }
@@ -5032,8 +5053,8 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
5032
5053
  warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
5033
5054
  return false;
5034
5055
  }
5035
- async function summarizeSession(chatId) {
5036
- return summarizeWithFallbackChain(chatId);
5056
+ async function summarizeSession(chatId, clearLogAfter = true) {
5057
+ return summarizeWithFallbackChain(chatId, void 0, void 0, clearLogAfter);
5037
5058
  }
5038
5059
  async function summarizeAllPending() {
5039
5060
  const allIds = getLoggedChatIds();
@@ -5051,7 +5072,7 @@ async function summarizeAllPending() {
5051
5072
  await summarizeSession(chatId);
5052
5073
  }
5053
5074
  }
5054
- var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, CONTEXT_BRIDGE_PROMPT;
5075
+ var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, CONTEXT_BRIDGE_PROMPT, VALID_SIGNAL_TYPES;
5055
5076
  var init_summarize = __esm({
5056
5077
  "src/memory/summarize.ts"() {
5057
5078
  "use strict";
@@ -5086,7 +5107,15 @@ SUMMARY: <detailed summary>
5086
5107
  KEY_DETAILS:
5087
5108
  - <detail 1>
5088
5109
  - <detail 2>
5089
- TOPICS: <specific-keyword1, specific-keyword2, ...>`;
5110
+ TOPICS: <specific-keyword1, specific-keyword2, ...>
5111
+
5112
+ SIGNALS (0-5 items, only if the conversation contains user feedback about the assistant's performance):
5113
+ - [correction] brief description of what the user corrected
5114
+ - [preference] brief description of a preference the user expressed
5115
+ - [frustration] brief description of user frustration or dissatisfaction
5116
+ - [praise] brief description of user satisfaction or approval
5117
+ - [error_pattern] brief description of a recurring error the user pointed out
5118
+ If no feedback signals are present, omit this section entirely.`;
5090
5119
  CONTEXT_BRIDGE_PROMPT = `You are handing off an active work session to a new AI assistant. Write a concise handoff brief (3-8 sentences) covering:
5091
5120
  - What the user is actively working on RIGHT NOW
5092
5121
  - Exact current state: what's done, what's in progress, what's blocked
@@ -5095,6 +5124,7 @@ TOPICS: <specific-keyword1, specific-keyword2, ...>`;
5095
5124
  - Any critical constraints or preferences expressed by the user
5096
5125
 
5097
5126
  Be specific. The new assistant has no prior context. Cover any domain \u2014 personal, research, scheduling, content, technical \u2014 whatever applies to this session.`;
5127
+ VALID_SIGNAL_TYPES = ["correction", "preference", "praise", "frustration", "error_pattern"];
5098
5128
  }
5099
5129
  });
5100
5130
 
@@ -6446,9 +6476,9 @@ function aggregateDailyMetrics(db3, chatId, date) {
6446
6476
  const signalCounts = db3.prepare(`
6447
6477
  SELECT signalType, COUNT(*) as count
6448
6478
  FROM feedback_signals
6449
- WHERE chatId = ? AND date(created_at) = ?
6479
+ WHERE chatId = ? AND created_at >= ? AND created_at < date(?, '+1 day')
6450
6480
  GROUP BY signalType
6451
- `).all(chatId, period);
6481
+ `).all(chatId, period, period);
6452
6482
  let corrections = 0;
6453
6483
  let praises = 0;
6454
6484
  let errors = 0;
@@ -6467,8 +6497,8 @@ function aggregateDailyMetrics(db3, chatId, date) {
6467
6497
  }
6468
6498
  const insightsRow = db3.prepare(`
6469
6499
  SELECT COUNT(*) as count FROM insights
6470
- WHERE chatId = ? AND date(appliedAt) = ?
6471
- `).get(chatId, period);
6500
+ WHERE chatId = ? AND appliedAt >= ? AND appliedAt < date(?, '+1 day')
6501
+ `).get(chatId, period, period);
6472
6502
  upsertGrowthMetric(db3, chatId, period, {
6473
6503
  corrections,
6474
6504
  praises,
@@ -6774,7 +6804,7 @@ function resolveReflectionAdapter(chatId) {
6774
6804
  if (config2.mode === "pinned" && config2.backend) {
6775
6805
  try {
6776
6806
  const adapter = getAdapter(config2.backend);
6777
- const model2 = config2.model ?? adapter.summarizerModel;
6807
+ const model2 = config2.model ?? adapter.defaultModel;
6778
6808
  return { adapter, model: model2 };
6779
6809
  } catch {
6780
6810
  return null;
@@ -6795,11 +6825,11 @@ function resolveReflectionAdapter(chatId) {
6795
6825
  }
6796
6826
  try {
6797
6827
  const adapter = getAdapterForChat(chatId);
6798
- return { adapter, model: adapter.summarizerModel };
6828
+ return { adapter, model: adapter.defaultModel };
6799
6829
  } catch {
6800
6830
  try {
6801
6831
  const adapter = getAdapter("claude");
6802
- return { adapter, model: adapter.summarizerModel };
6832
+ return { adapter, model: adapter.defaultModel };
6803
6833
  } catch {
6804
6834
  return null;
6805
6835
  }
@@ -6816,7 +6846,7 @@ function getTodayConversations(chatId) {
6816
6846
  const db3 = getDb();
6817
6847
  const rows = db3.prepare(`
6818
6848
  SELECT role, content FROM message_log
6819
- WHERE chat_id = ? AND date(created_at) = date('now')
6849
+ WHERE chat_id = ? AND created_at >= date('now') AND created_at < date('now', '+1 day')
6820
6850
  ORDER BY created_at ASC
6821
6851
  LIMIT 200
6822
6852
  `).all(chatId);
@@ -6835,7 +6865,7 @@ function getTodayConversations(chatId) {
6835
6865
  }
6836
6866
  return lines.join("\n\n");
6837
6867
  }
6838
- async function spawnAnalysis(adapter, model2, prompt) {
6868
+ async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEOUT_MS) {
6839
6869
  const config2 = adapter.buildSpawnConfig({
6840
6870
  prompt,
6841
6871
  model: model2,
@@ -6858,7 +6888,7 @@ async function spawnAnalysis(adapter, model2, prompt) {
6858
6888
  rl2.close();
6859
6889
  proc.kill("SIGTERM");
6860
6890
  setTimeout(() => proc.kill("SIGKILL"), 2e3);
6861
- }, ANALYSIS_TIMEOUT_MS);
6891
+ }, timeoutMs);
6862
6892
  rl2.on("line", (line) => {
6863
6893
  if (!line.trim()) return;
6864
6894
  let msg;
@@ -6950,7 +6980,7 @@ async function runAnalysis(chatId, opts = {}) {
6950
6980
  log(`[reflection] Running analysis via ${adapter.id}:${model2} for chat ${chatId} (${signals.length} signals, force=${force})`);
6951
6981
  let rawOutput;
6952
6982
  try {
6953
- rawOutput = await spawnAnalysis(adapter, model2, prompt);
6983
+ rawOutput = await spawnAnalysis(adapter, model2, prompt, opts.timeoutMs);
6954
6984
  } catch (err) {
6955
6985
  warn(`[reflection] Analysis spawn failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}`);
6956
6986
  return [];
@@ -7651,8 +7681,7 @@ function startDashboard() {
7651
7681
  const body = JSON.parse(await readBody(req));
7652
7682
  const { setBackend: setBackend2, clearSession: clearSession2, clearModel: clearModel2, clearThinkingLevel: clearThinkingLevel2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
7653
7683
  const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
7654
- summarizeSession2(body.chatId).catch(() => {
7655
- });
7684
+ await summarizeSession2(body.chatId);
7656
7685
  clearSession2(body.chatId);
7657
7686
  clearModel2(body.chatId);
7658
7687
  clearThinkingLevel2(body.chatId);
@@ -8577,7 +8606,8 @@ __export(agent_exports, {
8577
8606
  getInFlightMessage: () => getInFlightMessage,
8578
8607
  isChatBusy: () => isChatBusy,
8579
8608
  isSyntheticChatId: () => isSyntheticChatId,
8580
- stopAgent: () => stopAgent
8609
+ stopAgent: () => stopAgent,
8610
+ stopAllActiveAgents: () => stopAllActiveAgents
8581
8611
  });
8582
8612
  import { spawn as spawn5 } from "child_process";
8583
8613
  import { createInterface as createInterface4 } from "readline";
@@ -8605,13 +8635,22 @@ function withChatLock(chatId, fn) {
8605
8635
  }
8606
8636
  const next = prev.then(fn, fn);
8607
8637
  chatLocks.set(chatId, next.then(() => {
8638
+ chatLocks.delete(chatId);
8608
8639
  }, () => {
8640
+ chatLocks.delete(chatId);
8609
8641
  }));
8610
8642
  return next;
8611
8643
  }
8612
8644
  function isChatBusy(chatId) {
8613
8645
  return activeChats.has(chatId);
8614
8646
  }
8647
+ function stopAllActiveAgents() {
8648
+ for (const [, state] of activeChats) {
8649
+ if (state.process) {
8650
+ killProcessGroup(state.process);
8651
+ }
8652
+ }
8653
+ }
8615
8654
  function stopAgent(chatId) {
8616
8655
  const state = activeChats.get(chatId);
8617
8656
  if (!state) return false;
@@ -8867,7 +8906,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
8867
8906
  const effectiveAgentMode = optsAgentMode ?? getAgentMode(settingsChat);
8868
8907
  const sideQuestCtx = settingsSourceChatId ? { parentChatId: settingsSourceChatId, actualChatId: chatId } : void 0;
8869
8908
  const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx);
8870
- const existingSessionId = getSessionId(settingsChat);
8909
+ const existingSessionId = settingsSourceChatId ? null : getSessionId(settingsChat);
8871
8910
  const allowedTools = getEnabledTools(settingsChat);
8872
8911
  const mcpConfigPath = tier !== "slim" && effectiveAgentMode !== "native" && MCP_CONFIG_FLAG[adapter.id] ? getMcpConfigPath(chatId) : null;
8873
8912
  const baseConfig = adapter.buildSpawnConfig({
@@ -8925,17 +8964,19 @@ async function askAgentImpl(chatId, userMessage, opts) {
8925
8964
  }
8926
8965
  }
8927
8966
  } catch (spawnErr) {
8928
- try {
8929
- const errMsg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
8930
- const { logErrorSignal: logErrorSignal2 } = await Promise.resolve().then(() => (init_detect(), detect_exports));
8931
- if (/timeout/i.test(errMsg)) {
8932
- logErrorSignal2(chatId, "timeout", `Backend timed out`, { backendId: adapter.id, model: resolvedModel });
8933
- } else if (/repetition.?loop/i.test(errMsg)) {
8934
- logErrorSignal2(chatId, "repetition_loop", `Repetition loop detected`, { backendId: adapter.id, model: resolvedModel });
8935
- } else {
8936
- logErrorSignal2(chatId, "spawn_error", errMsg.slice(0, 300), { backendId: adapter.id, model: resolvedModel });
8967
+ if (!isSyntheticChatId(chatId)) {
8968
+ try {
8969
+ const errMsg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
8970
+ const { logErrorSignal: logErrorSignal2 } = await Promise.resolve().then(() => (init_detect(), detect_exports));
8971
+ if (/timeout/i.test(errMsg)) {
8972
+ logErrorSignal2(chatId, "timeout", `Backend timed out`, { backendId: adapter.id, model: resolvedModel });
8973
+ } else if (/repetition.?loop/i.test(errMsg)) {
8974
+ logErrorSignal2(chatId, "repetition_loop", `Repetition loop detected`, { backendId: adapter.id, model: resolvedModel });
8975
+ } else {
8976
+ logErrorSignal2(chatId, "spawn_error", errMsg.slice(0, 300), { backendId: adapter.id, model: resolvedModel });
8977
+ }
8978
+ } catch {
8937
8979
  }
8938
- } catch {
8939
8980
  }
8940
8981
  throw spawnErr;
8941
8982
  } finally {
@@ -9070,9 +9111,10 @@ ${responseText.slice(0, 500)}`);
9070
9111
  return true;
9071
9112
  }
9072
9113
  try {
9114
+ const text = responseText.replace(/\s*\[REACT:[^\]]*\]\s*/g, " ").trim();
9073
9115
  if (job.deliveryMode === "webhook") {
9074
- await deliverWebhook(job, responseText);
9075
- appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`, responseText, job.backend ?? "claude", job.model, null);
9116
+ await deliverWebhook(job, text);
9117
+ appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`, text, job.backend ?? "claude", job.model, null);
9076
9118
  return true;
9077
9119
  }
9078
9120
  const channelName = job.channel ?? "telegram";
@@ -9082,7 +9124,7 @@ ${responseText.slice(0, 500)}`);
9082
9124
  return false;
9083
9125
  }
9084
9126
  const targetChatId = job.target ?? job.chatId;
9085
- const cleanText = await processFileSends(targetChatId, channel, responseText);
9127
+ const cleanText = await processFileSends(targetChatId, channel, text);
9086
9128
  if (!cleanText) return true;
9087
9129
  if (channelName === "telegram") {
9088
9130
  const parsed = parseTelegramTarget(targetChatId);
@@ -9313,6 +9355,143 @@ var init_health2 = __esm({
9313
9355
  }
9314
9356
  });
9315
9357
 
9358
+ // src/reflection/propose.ts
9359
+ var propose_exports = {};
9360
+ __export(propose_exports, {
9361
+ buildEvolveMenuKeyboard: () => buildEvolveMenuKeyboard,
9362
+ buildEvolveOnboardingKeyboard: () => buildEvolveOnboardingKeyboard,
9363
+ buildModelKeyboard: () => buildModelKeyboard,
9364
+ buildProposalKeyboard: () => buildProposalKeyboard,
9365
+ buildUndoKeyboard: () => buildUndoKeyboard,
9366
+ formatDiffCodeBlock: () => formatDiffCodeBlock,
9367
+ formatGrowthReport: () => formatGrowthReport,
9368
+ formatNightlySummary: () => formatNightlySummary,
9369
+ formatProposalCard: () => formatProposalCard
9370
+ });
9371
+ function pct(ratio) {
9372
+ return `${Math.round(ratio * 100)}%`;
9373
+ }
9374
+ function formatProposalCard(params) {
9375
+ const { category, insight, why, targetFile, confidence, proposedAction, proposedDiff } = params;
9376
+ const header2 = `[${category}] ${insight}`;
9377
+ const whyLine = why ? `Why: ${why}` : null;
9378
+ const confidencePct = pct(confidence);
9379
+ const showTarget = category !== "codebase" && targetFile && targetFile !== "codebase";
9380
+ const targetLine = showTarget ? `Target: ${targetFile} | Confidence: ${confidencePct}` : `Confidence: ${confidencePct}`;
9381
+ const lines = [header2, ""];
9382
+ if (whyLine) lines.push(whyLine);
9383
+ lines.push(targetLine);
9384
+ if (proposedDiff) {
9385
+ const actionLabel = proposedAction ? ` (${proposedAction})` : "";
9386
+ lines.push("", `Proposed change${actionLabel}:`, "```diff", proposedDiff, "```");
9387
+ }
9388
+ return lines.join("\n");
9389
+ }
9390
+ function formatNightlySummary(insights) {
9391
+ const count = insights.length;
9392
+ const header2 = `Nightly Reflection \u2014 ${count} proposal${count === 1 ? "" : "s"} ready`;
9393
+ const list = insights.map((ins, i) => `${i + 1}. [${ins.category}] ${ins.insight}`).join("\n");
9394
+ return `${header2}
9395
+
9396
+ ${list}
9397
+
9398
+ Review with /evolve`;
9399
+ }
9400
+ function formatGrowthReport(metrics, modelPerformance) {
9401
+ const { correctionsBefore, correctionsAfter, praiseRatio, insightsApplied, pendingCount, topInsight } = metrics;
9402
+ const reduction = correctionsBefore > 0 ? Math.round((correctionsBefore - correctionsAfter) / correctionsBefore * 100) : 0;
9403
+ const lines = [];
9404
+ lines.push("Growth Report");
9405
+ lines.push("");
9406
+ lines.push(`Corrections: ${correctionsBefore.toFixed(1)} -> ${correctionsAfter.toFixed(1)} (${reduction}% reduction)`);
9407
+ lines.push(`Praise ratio: ${praiseRatio.toFixed(1)}x`);
9408
+ lines.push(`Insights applied: ${insightsApplied} | Pending: ${pendingCount}`);
9409
+ if (topInsight) {
9410
+ lines.push(`Top insight: "${topInsight.insight}" (${pct(topInsight.effectiveness)} effective)`);
9411
+ }
9412
+ if (modelPerformance.length > 0) {
9413
+ lines.push("");
9414
+ lines.push("Model Performance:");
9415
+ for (const row of modelPerformance) {
9416
+ const total = row.praises + row.corrections + row.errors;
9417
+ const scoreLabel = total > 0 ? ` (${row.praises}P / ${row.corrections}C / ${row.errors}E)` : "";
9418
+ lines.push(` ${row.backend} ${row.model} [${row.thinking}]${scoreLabel}`);
9419
+ }
9420
+ }
9421
+ return lines.join("\n");
9422
+ }
9423
+ function formatDiffCodeBlock(diff) {
9424
+ return `\`\`\`diff
9425
+ ${diff}
9426
+ \`\`\``;
9427
+ }
9428
+ function buildProposalKeyboard(insightId, category) {
9429
+ if (category === "codebase") {
9430
+ return [
9431
+ [
9432
+ { label: "Show Diff", data: `evolve:diff:${insightId}`, style: "primary" },
9433
+ { label: "Discuss", data: `evolve:discuss:${insightId}` },
9434
+ { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
9435
+ ]
9436
+ ];
9437
+ }
9438
+ return [
9439
+ [
9440
+ { label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
9441
+ { label: "Discuss", data: `evolve:discuss:${insightId}` },
9442
+ { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
9443
+ ]
9444
+ ];
9445
+ }
9446
+ function buildEvolveOnboardingKeyboard() {
9447
+ return [
9448
+ [{ label: "\u2705 Enable Self-Learning", data: "evolve:toggle", style: "success" }],
9449
+ [{ label: "One-Time Analysis", data: "evolve:analyze" }]
9450
+ ];
9451
+ }
9452
+ function buildEvolveMenuKeyboard(ctx) {
9453
+ const reviewLabel = ctx.pendingProposals > 0 ? `Review (${ctx.pendingProposals})` : "Review";
9454
+ return [
9455
+ [
9456
+ { label: "\u{1F50D} Analyze Now", data: "evolve:analyze", style: "success" },
9457
+ ctx.pendingProposals > 0 ? { label: reviewLabel, data: "evolve:review", style: "primary" } : { label: reviewLabel, data: "evolve:review" }
9458
+ ],
9459
+ [
9460
+ { label: "Stats", data: "evolve:stats" },
9461
+ { label: "History", data: "evolve:history" }
9462
+ ],
9463
+ [
9464
+ { label: "Model", data: "evolve:model" },
9465
+ { label: "Undo", data: "evolve:undo", style: "danger" },
9466
+ { label: "\u23F8 Disable", data: "evolve:toggle", style: "danger" }
9467
+ ]
9468
+ ];
9469
+ }
9470
+ function buildUndoKeyboard(insights) {
9471
+ return insights.map((ins) => [
9472
+ { label: `Undo #${ins.id}: ${ins.insight}`, data: `evolve:undo:${ins.id}`, style: "danger" }
9473
+ ]);
9474
+ }
9475
+ function buildModelKeyboard(currentMode) {
9476
+ const modes = [
9477
+ { label: "Auto", mode: "auto" },
9478
+ { label: "Pinned", mode: "pinned" },
9479
+ { label: "Cheap", mode: "cheap" }
9480
+ ];
9481
+ return [
9482
+ modes.map(({ label: label2, mode }) => ({
9483
+ label: mode === currentMode ? `[${label2}]` : label2,
9484
+ data: `evolve:model:${mode}`,
9485
+ ...mode === currentMode ? { style: "primary" } : {}
9486
+ }))
9487
+ ];
9488
+ }
9489
+ var init_propose = __esm({
9490
+ "src/reflection/propose.ts"() {
9491
+ "use strict";
9492
+ }
9493
+ });
9494
+
9316
9495
  // src/scheduler/cron.ts
9317
9496
  var cron_exports = {};
9318
9497
  __export(cron_exports, {
@@ -9533,6 +9712,17 @@ async function executeJob(job) {
9533
9712
  async function runWithRetry(job, model2, runId, t0) {
9534
9713
  let lastError;
9535
9714
  const chatId = job.sessionType === "isolated" ? `cron:${job.id}:${runId}` : job.chatId;
9715
+ if (job.jobType === "reflection") {
9716
+ const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
9717
+ const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
9718
+ const timeoutMs2 = job.timeout ? job.timeout * 1e3 : void 0;
9719
+ const insights = await runAnalysis2(job.chatId, { force: true, timeoutMs: timeoutMs2 });
9720
+ if (insights.length === 0) {
9721
+ return { text: "Nightly reflection: no new insights from recent interactions." };
9722
+ }
9723
+ const items = insights.map((ins, i) => ({ id: i + 1, ...ins }));
9724
+ return { text: formatNightlySummary2(items) };
9725
+ }
9536
9726
  if (job.thinking && job.thinking !== "auto") {
9537
9727
  setThinkingLevel(chatId, job.thinking);
9538
9728
  }
@@ -12786,143 +12976,6 @@ var init_pagination = __esm({
12786
12976
  }
12787
12977
  });
12788
12978
 
12789
- // src/reflection/propose.ts
12790
- var propose_exports = {};
12791
- __export(propose_exports, {
12792
- buildEvolveMenuKeyboard: () => buildEvolveMenuKeyboard,
12793
- buildEvolveOnboardingKeyboard: () => buildEvolveOnboardingKeyboard,
12794
- buildModelKeyboard: () => buildModelKeyboard,
12795
- buildProposalKeyboard: () => buildProposalKeyboard,
12796
- buildUndoKeyboard: () => buildUndoKeyboard,
12797
- formatDiffCodeBlock: () => formatDiffCodeBlock,
12798
- formatGrowthReport: () => formatGrowthReport,
12799
- formatNightlySummary: () => formatNightlySummary,
12800
- formatProposalCard: () => formatProposalCard
12801
- });
12802
- function pct(ratio) {
12803
- return `${Math.round(ratio * 100)}%`;
12804
- }
12805
- function formatProposalCard(params) {
12806
- const { category, insight, why, targetFile, confidence, proposedAction, proposedDiff } = params;
12807
- const header2 = `[${category}] ${insight}`;
12808
- const whyLine = why ? `Why: ${why}` : null;
12809
- const confidencePct = pct(confidence);
12810
- const showTarget = category !== "codebase" && targetFile && targetFile !== "codebase";
12811
- const targetLine = showTarget ? `Target: ${targetFile} | Confidence: ${confidencePct}` : `Confidence: ${confidencePct}`;
12812
- const lines = [header2, ""];
12813
- if (whyLine) lines.push(whyLine);
12814
- lines.push(targetLine);
12815
- if (proposedDiff) {
12816
- const actionLabel = proposedAction ? ` (${proposedAction})` : "";
12817
- lines.push("", `Proposed change${actionLabel}:`, "```diff", proposedDiff, "```");
12818
- }
12819
- return lines.join("\n");
12820
- }
12821
- function formatNightlySummary(insights) {
12822
- const count = insights.length;
12823
- const header2 = `Nightly Reflection \u2014 ${count} proposal${count === 1 ? "" : "s"} ready`;
12824
- const list = insights.map((ins, i) => `${i + 1}. [${ins.category}] ${ins.insight}`).join("\n");
12825
- return `${header2}
12826
-
12827
- ${list}
12828
-
12829
- Review with /evolve`;
12830
- }
12831
- function formatGrowthReport(metrics, modelPerformance) {
12832
- const { correctionsBefore, correctionsAfter, praiseRatio, insightsApplied, pendingCount, topInsight } = metrics;
12833
- const reduction = correctionsBefore > 0 ? Math.round((correctionsBefore - correctionsAfter) / correctionsBefore * 100) : 0;
12834
- const lines = [];
12835
- lines.push("Growth Report");
12836
- lines.push("");
12837
- lines.push(`Corrections: ${correctionsBefore.toFixed(1)} -> ${correctionsAfter.toFixed(1)} (${reduction}% reduction)`);
12838
- lines.push(`Praise ratio: ${praiseRatio.toFixed(1)}x`);
12839
- lines.push(`Insights applied: ${insightsApplied} | Pending: ${pendingCount}`);
12840
- if (topInsight) {
12841
- lines.push(`Top insight: "${topInsight.insight}" (${pct(topInsight.effectiveness)} effective)`);
12842
- }
12843
- if (modelPerformance.length > 0) {
12844
- lines.push("");
12845
- lines.push("Model Performance:");
12846
- for (const row of modelPerformance) {
12847
- const total = row.praises + row.corrections + row.errors;
12848
- const scoreLabel = total > 0 ? ` (${row.praises}P / ${row.corrections}C / ${row.errors}E)` : "";
12849
- lines.push(` ${row.backend} ${row.model} [${row.thinking}]${scoreLabel}`);
12850
- }
12851
- }
12852
- return lines.join("\n");
12853
- }
12854
- function formatDiffCodeBlock(diff) {
12855
- return `\`\`\`diff
12856
- ${diff}
12857
- \`\`\``;
12858
- }
12859
- function buildProposalKeyboard(insightId, category) {
12860
- if (category === "codebase") {
12861
- return [
12862
- [
12863
- { label: "Show Diff", data: `evolve:diff:${insightId}`, style: "primary" },
12864
- { label: "Discuss", data: `evolve:discuss:${insightId}` },
12865
- { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
12866
- ]
12867
- ];
12868
- }
12869
- return [
12870
- [
12871
- { label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
12872
- { label: "Discuss", data: `evolve:discuss:${insightId}` },
12873
- { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
12874
- ]
12875
- ];
12876
- }
12877
- function buildEvolveOnboardingKeyboard() {
12878
- return [
12879
- [{ label: "\u2705 Enable Self-Learning", data: "evolve:toggle", style: "success" }],
12880
- [{ label: "One-Time Analysis", data: "evolve:analyze" }]
12881
- ];
12882
- }
12883
- function buildEvolveMenuKeyboard(ctx) {
12884
- const reviewLabel = ctx.pendingProposals > 0 ? `Review (${ctx.pendingProposals})` : "Review";
12885
- return [
12886
- [
12887
- { label: "\u{1F50D} Analyze Now", data: "evolve:analyze", style: "success" },
12888
- ctx.pendingProposals > 0 ? { label: reviewLabel, data: "evolve:review", style: "primary" } : { label: reviewLabel, data: "evolve:review" }
12889
- ],
12890
- [
12891
- { label: "Stats", data: "evolve:stats" },
12892
- { label: "History", data: "evolve:history" }
12893
- ],
12894
- [
12895
- { label: "Model", data: "evolve:model" },
12896
- { label: "Undo", data: "evolve:undo", style: "danger" },
12897
- { label: "\u23F8 Disable", data: "evolve:toggle", style: "danger" }
12898
- ]
12899
- ];
12900
- }
12901
- function buildUndoKeyboard(insights) {
12902
- return insights.map((ins) => [
12903
- { label: `Undo #${ins.id}: ${ins.insight}`, data: `evolve:undo:${ins.id}`, style: "danger" }
12904
- ]);
12905
- }
12906
- function buildModelKeyboard(currentMode) {
12907
- const modes = [
12908
- { label: "Auto", mode: "auto" },
12909
- { label: "Pinned", mode: "pinned" },
12910
- { label: "Cheap", mode: "cheap" }
12911
- ];
12912
- return [
12913
- modes.map(({ label: label2, mode }) => ({
12914
- label: mode === currentMode ? `[${label2}]` : label2,
12915
- data: `evolve:model:${mode}`,
12916
- ...mode === currentMode ? { style: "primary" } : {}
12917
- }))
12918
- ];
12919
- }
12920
- var init_propose = __esm({
12921
- "src/reflection/propose.ts"() {
12922
- "use strict";
12923
- }
12924
- });
12925
-
12926
12979
  // src/router.ts
12927
12980
  import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir2, readdir as readdir3, stat } from "fs/promises";
12928
12981
  import { existsSync as existsSync19 } from "fs";
@@ -14838,32 +14891,49 @@ Message: "${testMsg}"`, { parseMode: "plain" });
14838
14891
  break;
14839
14892
  }
14840
14893
  case "reflect": {
14841
- const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
14842
- const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14843
- await channel.sendText(chatId, "Analyzing recent interactions...", { parseMode: "plain" });
14894
+ const lastReflect = lastReflectTime.get(chatId);
14895
+ if (lastReflect && Date.now() - lastReflect < REFLECT_COOLDOWN_MS) {
14896
+ const remaining = Math.ceil((REFLECT_COOLDOWN_MS - (Date.now() - lastReflect)) / 1e3);
14897
+ await channel.sendText(chatId, `Please wait ${remaining}s before reflecting again.`, { parseMode: "plain" });
14898
+ break;
14899
+ }
14900
+ const { getMessagePairCount: getMessagePairCount2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
14901
+ const pairCount = getMessagePairCount2(chatId);
14902
+ if (pairCount < 2) {
14903
+ await channel.sendText(chatId, "Need more conversation to analyze \u2014 at least 2 message exchanges.", { parseMode: "plain" });
14904
+ break;
14905
+ }
14906
+ lastReflectTime.set(chatId, Date.now());
14907
+ await channel.sendText(chatId, "Step 1/3: Summarizing conversation...", { parseMode: "plain" });
14844
14908
  try {
14909
+ const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
14910
+ await summarizeSession2(chatId, false);
14911
+ } catch (e) {
14912
+ await channel.sendText(chatId, `Summarization failed: ${e}`, { parseMode: "plain" });
14913
+ break;
14914
+ }
14915
+ await channel.sendText(chatId, "Step 2/3: Analyzing for insights...", { parseMode: "plain" });
14916
+ try {
14917
+ const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
14918
+ const { formatProposalCard: formatProposalCard2, buildProposalKeyboard: buildProposalKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14845
14919
  const insights = await runAnalysis2(chatId, { force: true });
14846
14920
  if (insights.length === 0) {
14847
- const { getReflectionStatus: getRefStatus } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14848
- const isFrozen = getRefStatus(getDb(), chatId) === "frozen";
14849
- if (isFrozen) {
14850
- const msg2 = "No insights found. Self-learning is disabled \u2014\nenable it with /evolve so feedback signals are\ncaptured automatically between analyses.";
14851
- if (typeof channel.sendKeyboard === "function") {
14852
- await channel.sendKeyboard(chatId, msg2, [[
14853
- { label: "Open /evolve", data: "evolve:menu", style: "primary" }
14854
- ]]);
14855
- } else {
14856
- await channel.sendText(chatId, msg2, { parseMode: "plain" });
14857
- }
14858
- } else {
14859
- await channel.sendText(chatId, "No new insights from recent interactions.", { parseMode: "plain" });
14860
- }
14921
+ await channel.sendText(chatId, "Step 3/3: No actionable improvements found in this session.", { parseMode: "plain" });
14861
14922
  } else {
14862
- const items = insights.map((ins, i) => ({ id: i + 1, category: ins.category, insight: ins.insight }));
14863
- await channel.sendText(chatId, formatNightlySummary2(items) + "\n\nUse /evolve to review and apply proposals.", { parseMode: "plain" });
14923
+ await channel.sendText(chatId, `Step 3/3: Found ${insights.length} proposal(s).`, { parseMode: "plain" });
14924
+ const { getPendingInsights: getPendingInsights2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14925
+ const pending = getPendingInsights2(getDb(), chatId);
14926
+ for (const insight of pending.slice(0, 5)) {
14927
+ const card = formatProposalCard2(insight);
14928
+ const kb = buildProposalKeyboard2(insight.id, insight.category);
14929
+ await channel.sendKeyboard(chatId, card, kb);
14930
+ }
14931
+ if (insights.length > 5) {
14932
+ await channel.sendText(chatId, `${insights.length - 5} more proposals. Use /evolve to review all.`, { parseMode: "plain" });
14933
+ }
14864
14934
  }
14865
14935
  } catch (e) {
14866
- await channel.sendText(chatId, `Analysis failed: ${e}`, { parseMode: "plain" });
14936
+ await channel.sendText(chatId, `Analysis failed: ${e}. You can review any pending proposals with /evolve.`, { parseMode: "plain" });
14867
14937
  }
14868
14938
  break;
14869
14939
  }
@@ -15317,7 +15387,8 @@ async function handleSideQuest(parentChatId, msg, channel) {
15317
15387
  backendId: adapter.id,
15318
15388
  model: model2 ?? adapter.defaultModel
15319
15389
  });
15320
- } catch {
15390
+ } catch (e) {
15391
+ log(`[reflection] Side quest signal detection error: ${e}`);
15321
15392
  }
15322
15393
  } catch (err) {
15323
15394
  typingActive = false;
@@ -16245,9 +16316,12 @@ ${PERM_MODES[chosen]}`,
16245
16316
  const pending2 = pendingInterrupts.get(targetChatId);
16246
16317
  if (pending2) {
16247
16318
  pendingInterrupts.delete(targetChatId);
16248
- handleSideQuest(targetChatId, pending2.msg, pending2.channel).catch(
16249
- (err) => error(`[router] Side quest error for ${targetChatId}:`, err)
16250
- );
16319
+ await channel.sendText(chatId, "\u{1F5FA} Launching side quest\u2026", { parseMode: "plain" });
16320
+ handleSideQuest(targetChatId, pending2.msg, pending2.channel).catch((err) => {
16321
+ error(`[router] Side quest error for ${targetChatId}:`, err);
16322
+ channel.sendText(targetChatId, `\u{1F5FA} Side quest failed: ${err.message}`, { parseMode: "plain" }).catch(() => {
16323
+ });
16324
+ });
16251
16325
  } else {
16252
16326
  await channel.sendText(chatId, "Main task finished \u2014 your message was already processed.", { parseMode: "plain" });
16253
16327
  }
@@ -17457,7 +17531,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
17457
17531
  const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
17458
17532
  await channel.sendKeyboard(chatId, header2, buttons);
17459
17533
  }
17460
- var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
17534
+ var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings, lastReflectTime, REFLECT_COOLDOWN_MS, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
17461
17535
  var init_router = __esm({
17462
17536
  "src/router.ts"() {
17463
17537
  "use strict";
@@ -17600,6 +17674,8 @@ var init_router = __esm({
17600
17674
  MAX_SIDE_QUESTS = 2;
17601
17675
  pendingFallbackMessages = /* @__PURE__ */ new Map();
17602
17676
  dashboardClawWarnings = /* @__PURE__ */ new Map();
17677
+ lastReflectTime = /* @__PURE__ */ new Map();
17678
+ REFLECT_COOLDOWN_MS = 12e4;
17603
17679
  pendingSummaryUndo = /* @__PURE__ */ new Map();
17604
17680
  pendingNewchatUndo = /* @__PURE__ */ new Map();
17605
17681
  CLI_INSTALL_HINTS = {
@@ -18614,6 +18690,8 @@ async function main() {
18614
18690
  const shutdown = async (signal) => {
18615
18691
  log(`[cc-claw] Received ${signal}, shutting down...`);
18616
18692
  try {
18693
+ const { stopAllActiveAgents: stopAllActiveAgents2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
18694
+ stopAllActiveAgents2();
18617
18695
  stopAllHeartbeats();
18618
18696
  stopHealthMonitor3();
18619
18697
  stopMonitor();
@@ -19381,14 +19459,18 @@ async function doctorCommand(globalOpts, localOpts) {
19381
19459
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
19382
19460
  }
19383
19461
  const CLI_BINARIES = { claude: "claude", gemini: "gemini", codex: "codex", cursor: "agent" };
19462
+ let installedBackends = 0;
19384
19463
  for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
19385
19464
  try {
19386
19465
  const path = execFileSync3("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
19387
19466
  checks.push({ name: `${label2} CLI`, status: "ok", message: path });
19467
+ installedBackends++;
19388
19468
  } catch {
19389
- checks.push({ name: `${label2} CLI`, status: "warning", message: "not found in PATH" });
19390
19469
  }
19391
19470
  }
19471
+ if (installedBackends === 0) {
19472
+ checks.push({ name: "Backend CLIs", status: "error", message: "No backend CLIs found. Install at least one (claude, gemini, codex, or cursor agent)." });
19473
+ }
19392
19474
  try {
19393
19475
  const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
19394
19476
  const running = await isDaemonRunning2();
@@ -19418,6 +19500,13 @@ async function doctorCommand(globalOpts, localOpts) {
19418
19500
  } catch {
19419
19501
  checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
19420
19502
  }
19503
+ try {
19504
+ const latest = execFileSync3("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
19505
+ if (latest && latest !== VERSION) {
19506
+ checks.push({ name: "Update available", status: "warning", message: `v${latest} available (current: v${VERSION})`, fix: "npm install -g cc-claw@latest" });
19507
+ }
19508
+ } catch {
19509
+ }
19421
19510
  const tokenPath = `${DATA_PATH}/api-token`;
19422
19511
  if (existsSync26(tokenPath)) {
19423
19512
  try {
@@ -23333,6 +23422,20 @@ program.command("setup").description("Interactive configuration wizard").action(
23333
23422
  await Promise.resolve().then(() => (init_setup(), setup_exports));
23334
23423
  });
23335
23424
  async function run(argv = process.argv) {
23425
+ if (argv.includes("--version") || argv.includes("-V")) {
23426
+ console.log(VERSION);
23427
+ try {
23428
+ const { execFileSync: execFileSync5 } = await import("child_process");
23429
+ const latest = execFileSync5("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
23430
+ if (latest && latest !== VERSION) {
23431
+ console.log(`
23432
+ Update available: v${latest} (current: v${VERSION})`);
23433
+ console.log(`Run: npm install -g cc-claw@latest`);
23434
+ }
23435
+ } catch {
23436
+ }
23437
+ return;
23438
+ }
23336
23439
  if (argv.includes("--ai")) {
23337
23440
  const { generateAiSkill: generateAiSkill2, installAiSkill: installAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
23338
23441
  if (argv.includes("--install")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.11.3",
3
+ "version": "0.12.1",
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",