cc-claw 0.11.2 → 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 +358 -252
  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.2" : (() => {
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,27 +2533,31 @@ 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
  }
2555
2541
  function backfillJobTitles(database) {
2556
- const rows = database.prepare("SELECT id, description FROM jobs WHERE title IS NULL AND active = 1").all();
2542
+ const rows = database.prepare(
2543
+ "SELECT id, description FROM jobs WHERE active = 1 AND (title IS NULL OR title LIKE 'No thinking%' OR title LIKE 'You must%' OR title LIKE 'You MUST%')"
2544
+ ).all();
2557
2545
  if (rows.length === 0) return;
2558
- const preamblePatterns = [
2559
- /^you\s+must\s+call\s+the\s+\w+\s+tool\b[^.]*\.\s*/i,
2560
- /^important:\s*[^.]*\.\s*/i,
2561
- /^always\s+use\s+[^.]*\.\s*/i,
2562
- /^note:\s*[^.]*\.\s*/i
2563
- ];
2546
+ const instructionLinePattern = /^(?:you\s+must|important|always|note|never|no\s+thinking|do\s+not|don't|remember|make\s+sure|ensure|critical|warning|before\s+you|first,?\s+you|use\s+the|call\s+the)\b[^.\n]*[.!]?\s*/i;
2564
2547
  for (const row of rows) {
2565
2548
  let text = row.description;
2566
- for (const pat of preamblePatterns) {
2567
- text = text.replace(pat, "");
2568
- }
2569
- text = text.trim();
2570
- const firstSentence = text.match(/^[^.!?\n]{5,60}[.!?]?/)?.[0] ?? text.slice(0, 60);
2571
- const title = firstSentence.length > 60 ? firstSentence.slice(0, 57) + "\u2026" : firstSentence;
2549
+ let prevLen = 0;
2550
+ while (text.length !== prevLen && text.length > 0) {
2551
+ prevLen = text.length;
2552
+ text = text.replace(instructionLinePattern, "").trim();
2553
+ text = text.replace(/^\n+/, "").trim();
2554
+ }
2555
+ if (text.length < 10) text = row.description;
2556
+ text = text.replace(/https?:\/\/\S+/g, "").trim();
2557
+ text = text.replace(/\([^)]*\)\s*$/, "").trim();
2558
+ const firstLine = text.split(/\n/)[0].trim();
2559
+ const firstSentence = firstLine.match(/^.{5,55}(?:\b|[.!?])/)?.[0] ?? firstLine.slice(0, 55);
2560
+ const title = firstSentence.length > 55 ? firstSentence.slice(0, 52) + "\u2026" : firstSentence;
2572
2561
  database.prepare("UPDATE jobs SET title = ? WHERE id = ?").run(title, row.id);
2573
2562
  }
2574
2563
  }
@@ -2626,7 +2615,8 @@ function updateJob(id, fields) {
2626
2615
  channel: "channel",
2627
2616
  target: "target",
2628
2617
  deliveryMode: "delivery_mode",
2629
- timezone: "timezone"
2618
+ timezone: "timezone",
2619
+ jobType: "job_type"
2630
2620
  };
2631
2621
  for (const [key, val] of Object.entries(fields)) {
2632
2622
  const col = fieldMap[key];
@@ -3063,7 +3053,7 @@ var init_store5 = __esm({
3063
3053
  SELECT id, schedule_type as scheduleType, cron, at_time as atTime, every_ms as everyMs,
3064
3054
  title, description, chat_id as chatId, backend, model, thinking, timeout, fallbacks,
3065
3055
  session_type as sessionType, channel, target, delivery_mode as deliveryMode,
3066
- 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,
3067
3057
  next_run_at as nextRunAt, consecutive_failures as consecutiveFailures
3068
3058
  FROM jobs
3069
3059
  `;
@@ -4955,7 +4945,7 @@ ${transcript}`;
4955
4945
  if (!resultText) resultText = accumulatedText;
4956
4946
  if (!resultText) {
4957
4947
  warn(`[summarize] ${adapter.id}:${model2} returned empty result for chat ${chatId}`);
4958
- return false;
4948
+ return { success: false, rawText: "" };
4959
4949
  }
4960
4950
  const summaryMatch = resultText.match(/SUMMARY:\s*(.+?)(?=\nKEY_DETAILS:|\nTOPICS:|$)/s);
4961
4951
  const keyDetailsMatch = resultText.match(/KEY_DETAILS:\s*(.+?)(?=\nTOPICS:|$)/s);
@@ -4967,16 +4957,44 @@ Key details: ${keyDetails}`;
4967
4957
  const topics = topicsMatch?.[1]?.trim() ?? "";
4968
4958
  saveSessionSummaryWithEmbedding(chatId, summary, topics, Math.floor(entries.length / 2));
4969
4959
  log(`[summarize] Saved summary via ${adapter.id}:${model2} for chat ${chatId}`);
4970
- return true;
4960
+ return { success: true, rawText: resultText };
4971
4961
  } catch (err) {
4972
4962
  warn(`[summarize] ${adapter.id}:${model2} failed for ${chatId}: ${errorMessage(err)}`);
4973
- return false;
4963
+ return { success: false, rawText: "" };
4964
+ }
4965
+ }
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}`);
4974
4992
  }
4975
4993
  }
4976
- async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend) {
4994
+ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend, clearLogAfter = true) {
4977
4995
  const pairCount = getMessagePairCount(chatId);
4978
4996
  if (pairCount < MIN_PAIRS) {
4979
- clearLog(chatId);
4997
+ if (clearLogAfter) clearLog(chatId);
4980
4998
  return false;
4981
4999
  }
4982
5000
  const entries = getLog(chatId);
@@ -4993,8 +5011,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
4993
5011
  const model2 = config2.model ?? adapter.summarizerModel;
4994
5012
  const key = `${adapter.id}:${model2}`;
4995
5013
  tried.add(key);
4996
- if (await attemptSummarize(chatId, adapter, model2, entries)) {
4997
- 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);
4998
5018
  return true;
4999
5019
  }
5000
5020
  }
@@ -5007,8 +5027,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
5007
5027
  const key = `${targetAdapter.id}:${model2}`;
5008
5028
  if (!tried.has(key)) {
5009
5029
  tried.add(key);
5010
- if (await attemptSummarize(chatId, targetAdapter, model2, entries)) {
5011
- 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);
5012
5034
  return true;
5013
5035
  }
5014
5036
  }
@@ -5020,8 +5042,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
5020
5042
  const key = `${adapter.id}:${model2}`;
5021
5043
  if (!tried.has(key)) {
5022
5044
  tried.add(key);
5023
- if (await attemptSummarize(chatId, adapter, model2, entries)) {
5024
- 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);
5025
5049
  return true;
5026
5050
  }
5027
5051
  }
@@ -5029,8 +5053,8 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
5029
5053
  warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
5030
5054
  return false;
5031
5055
  }
5032
- async function summarizeSession(chatId) {
5033
- return summarizeWithFallbackChain(chatId);
5056
+ async function summarizeSession(chatId, clearLogAfter = true) {
5057
+ return summarizeWithFallbackChain(chatId, void 0, void 0, clearLogAfter);
5034
5058
  }
5035
5059
  async function summarizeAllPending() {
5036
5060
  const allIds = getLoggedChatIds();
@@ -5048,7 +5072,7 @@ async function summarizeAllPending() {
5048
5072
  await summarizeSession(chatId);
5049
5073
  }
5050
5074
  }
5051
- 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;
5052
5076
  var init_summarize = __esm({
5053
5077
  "src/memory/summarize.ts"() {
5054
5078
  "use strict";
@@ -5083,7 +5107,15 @@ SUMMARY: <detailed summary>
5083
5107
  KEY_DETAILS:
5084
5108
  - <detail 1>
5085
5109
  - <detail 2>
5086
- 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.`;
5087
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:
5088
5120
  - What the user is actively working on RIGHT NOW
5089
5121
  - Exact current state: what's done, what's in progress, what's blocked
@@ -5092,6 +5124,7 @@ TOPICS: <specific-keyword1, specific-keyword2, ...>`;
5092
5124
  - Any critical constraints or preferences expressed by the user
5093
5125
 
5094
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"];
5095
5128
  }
5096
5129
  });
5097
5130
 
@@ -6443,9 +6476,9 @@ function aggregateDailyMetrics(db3, chatId, date) {
6443
6476
  const signalCounts = db3.prepare(`
6444
6477
  SELECT signalType, COUNT(*) as count
6445
6478
  FROM feedback_signals
6446
- WHERE chatId = ? AND date(created_at) = ?
6479
+ WHERE chatId = ? AND created_at >= ? AND created_at < date(?, '+1 day')
6447
6480
  GROUP BY signalType
6448
- `).all(chatId, period);
6481
+ `).all(chatId, period, period);
6449
6482
  let corrections = 0;
6450
6483
  let praises = 0;
6451
6484
  let errors = 0;
@@ -6464,8 +6497,8 @@ function aggregateDailyMetrics(db3, chatId, date) {
6464
6497
  }
6465
6498
  const insightsRow = db3.prepare(`
6466
6499
  SELECT COUNT(*) as count FROM insights
6467
- WHERE chatId = ? AND date(appliedAt) = ?
6468
- `).get(chatId, period);
6500
+ WHERE chatId = ? AND appliedAt >= ? AND appliedAt < date(?, '+1 day')
6501
+ `).get(chatId, period, period);
6469
6502
  upsertGrowthMetric(db3, chatId, period, {
6470
6503
  corrections,
6471
6504
  praises,
@@ -6771,7 +6804,7 @@ function resolveReflectionAdapter(chatId) {
6771
6804
  if (config2.mode === "pinned" && config2.backend) {
6772
6805
  try {
6773
6806
  const adapter = getAdapter(config2.backend);
6774
- const model2 = config2.model ?? adapter.summarizerModel;
6807
+ const model2 = config2.model ?? adapter.defaultModel;
6775
6808
  return { adapter, model: model2 };
6776
6809
  } catch {
6777
6810
  return null;
@@ -6792,11 +6825,11 @@ function resolveReflectionAdapter(chatId) {
6792
6825
  }
6793
6826
  try {
6794
6827
  const adapter = getAdapterForChat(chatId);
6795
- return { adapter, model: adapter.summarizerModel };
6828
+ return { adapter, model: adapter.defaultModel };
6796
6829
  } catch {
6797
6830
  try {
6798
6831
  const adapter = getAdapter("claude");
6799
- return { adapter, model: adapter.summarizerModel };
6832
+ return { adapter, model: adapter.defaultModel };
6800
6833
  } catch {
6801
6834
  return null;
6802
6835
  }
@@ -6813,7 +6846,7 @@ function getTodayConversations(chatId) {
6813
6846
  const db3 = getDb();
6814
6847
  const rows = db3.prepare(`
6815
6848
  SELECT role, content FROM message_log
6816
- 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')
6817
6850
  ORDER BY created_at ASC
6818
6851
  LIMIT 200
6819
6852
  `).all(chatId);
@@ -6832,7 +6865,7 @@ function getTodayConversations(chatId) {
6832
6865
  }
6833
6866
  return lines.join("\n\n");
6834
6867
  }
6835
- async function spawnAnalysis(adapter, model2, prompt) {
6868
+ async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEOUT_MS) {
6836
6869
  const config2 = adapter.buildSpawnConfig({
6837
6870
  prompt,
6838
6871
  model: model2,
@@ -6855,7 +6888,7 @@ async function spawnAnalysis(adapter, model2, prompt) {
6855
6888
  rl2.close();
6856
6889
  proc.kill("SIGTERM");
6857
6890
  setTimeout(() => proc.kill("SIGKILL"), 2e3);
6858
- }, ANALYSIS_TIMEOUT_MS);
6891
+ }, timeoutMs);
6859
6892
  rl2.on("line", (line) => {
6860
6893
  if (!line.trim()) return;
6861
6894
  let msg;
@@ -6947,7 +6980,7 @@ async function runAnalysis(chatId, opts = {}) {
6947
6980
  log(`[reflection] Running analysis via ${adapter.id}:${model2} for chat ${chatId} (${signals.length} signals, force=${force})`);
6948
6981
  let rawOutput;
6949
6982
  try {
6950
- rawOutput = await spawnAnalysis(adapter, model2, prompt);
6983
+ rawOutput = await spawnAnalysis(adapter, model2, prompt, opts.timeoutMs);
6951
6984
  } catch (err) {
6952
6985
  warn(`[reflection] Analysis spawn failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}`);
6953
6986
  return [];
@@ -7648,8 +7681,7 @@ function startDashboard() {
7648
7681
  const body = JSON.parse(await readBody(req));
7649
7682
  const { setBackend: setBackend2, clearSession: clearSession2, clearModel: clearModel2, clearThinkingLevel: clearThinkingLevel2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
7650
7683
  const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
7651
- summarizeSession2(body.chatId).catch(() => {
7652
- });
7684
+ await summarizeSession2(body.chatId);
7653
7685
  clearSession2(body.chatId);
7654
7686
  clearModel2(body.chatId);
7655
7687
  clearThinkingLevel2(body.chatId);
@@ -8574,7 +8606,8 @@ __export(agent_exports, {
8574
8606
  getInFlightMessage: () => getInFlightMessage,
8575
8607
  isChatBusy: () => isChatBusy,
8576
8608
  isSyntheticChatId: () => isSyntheticChatId,
8577
- stopAgent: () => stopAgent
8609
+ stopAgent: () => stopAgent,
8610
+ stopAllActiveAgents: () => stopAllActiveAgents
8578
8611
  });
8579
8612
  import { spawn as spawn5 } from "child_process";
8580
8613
  import { createInterface as createInterface4 } from "readline";
@@ -8602,13 +8635,22 @@ function withChatLock(chatId, fn) {
8602
8635
  }
8603
8636
  const next = prev.then(fn, fn);
8604
8637
  chatLocks.set(chatId, next.then(() => {
8638
+ chatLocks.delete(chatId);
8605
8639
  }, () => {
8640
+ chatLocks.delete(chatId);
8606
8641
  }));
8607
8642
  return next;
8608
8643
  }
8609
8644
  function isChatBusy(chatId) {
8610
8645
  return activeChats.has(chatId);
8611
8646
  }
8647
+ function stopAllActiveAgents() {
8648
+ for (const [, state] of activeChats) {
8649
+ if (state.process) {
8650
+ killProcessGroup(state.process);
8651
+ }
8652
+ }
8653
+ }
8612
8654
  function stopAgent(chatId) {
8613
8655
  const state = activeChats.get(chatId);
8614
8656
  if (!state) return false;
@@ -8864,7 +8906,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
8864
8906
  const effectiveAgentMode = optsAgentMode ?? getAgentMode(settingsChat);
8865
8907
  const sideQuestCtx = settingsSourceChatId ? { parentChatId: settingsSourceChatId, actualChatId: chatId } : void 0;
8866
8908
  const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx);
8867
- const existingSessionId = getSessionId(settingsChat);
8909
+ const existingSessionId = settingsSourceChatId ? null : getSessionId(settingsChat);
8868
8910
  const allowedTools = getEnabledTools(settingsChat);
8869
8911
  const mcpConfigPath = tier !== "slim" && effectiveAgentMode !== "native" && MCP_CONFIG_FLAG[adapter.id] ? getMcpConfigPath(chatId) : null;
8870
8912
  const baseConfig = adapter.buildSpawnConfig({
@@ -8922,17 +8964,19 @@ async function askAgentImpl(chatId, userMessage, opts) {
8922
8964
  }
8923
8965
  }
8924
8966
  } catch (spawnErr) {
8925
- try {
8926
- const errMsg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
8927
- const { logErrorSignal: logErrorSignal2 } = await Promise.resolve().then(() => (init_detect(), detect_exports));
8928
- if (/timeout/i.test(errMsg)) {
8929
- logErrorSignal2(chatId, "timeout", `Backend timed out`, { backendId: adapter.id, model: resolvedModel });
8930
- } else if (/repetition.?loop/i.test(errMsg)) {
8931
- logErrorSignal2(chatId, "repetition_loop", `Repetition loop detected`, { backendId: adapter.id, model: resolvedModel });
8932
- } else {
8933
- 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 {
8934
8979
  }
8935
- } catch {
8936
8980
  }
8937
8981
  throw spawnErr;
8938
8982
  } finally {
@@ -9067,9 +9111,10 @@ ${responseText.slice(0, 500)}`);
9067
9111
  return true;
9068
9112
  }
9069
9113
  try {
9114
+ const text = responseText.replace(/\s*\[REACT:[^\]]*\]\s*/g, " ").trim();
9070
9115
  if (job.deliveryMode === "webhook") {
9071
- await deliverWebhook(job, responseText);
9072
- 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);
9073
9118
  return true;
9074
9119
  }
9075
9120
  const channelName = job.channel ?? "telegram";
@@ -9079,7 +9124,7 @@ ${responseText.slice(0, 500)}`);
9079
9124
  return false;
9080
9125
  }
9081
9126
  const targetChatId = job.target ?? job.chatId;
9082
- const cleanText = await processFileSends(targetChatId, channel, responseText);
9127
+ const cleanText = await processFileSends(targetChatId, channel, text);
9083
9128
  if (!cleanText) return true;
9084
9129
  if (channelName === "telegram") {
9085
9130
  const parsed = parseTelegramTarget(targetChatId);
@@ -9310,6 +9355,143 @@ var init_health2 = __esm({
9310
9355
  }
9311
9356
  });
9312
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
+
9313
9495
  // src/scheduler/cron.ts
9314
9496
  var cron_exports = {};
9315
9497
  __export(cron_exports, {
@@ -9530,6 +9712,17 @@ async function executeJob(job) {
9530
9712
  async function runWithRetry(job, model2, runId, t0) {
9531
9713
  let lastError;
9532
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
+ }
9533
9726
  if (job.thinking && job.thinking !== "auto") {
9534
9727
  setThinkingLevel(chatId, job.thinking);
9535
9728
  }
@@ -12783,143 +12976,6 @@ var init_pagination = __esm({
12783
12976
  }
12784
12977
  });
12785
12978
 
12786
- // src/reflection/propose.ts
12787
- var propose_exports = {};
12788
- __export(propose_exports, {
12789
- buildEvolveMenuKeyboard: () => buildEvolveMenuKeyboard,
12790
- buildEvolveOnboardingKeyboard: () => buildEvolveOnboardingKeyboard,
12791
- buildModelKeyboard: () => buildModelKeyboard,
12792
- buildProposalKeyboard: () => buildProposalKeyboard,
12793
- buildUndoKeyboard: () => buildUndoKeyboard,
12794
- formatDiffCodeBlock: () => formatDiffCodeBlock,
12795
- formatGrowthReport: () => formatGrowthReport,
12796
- formatNightlySummary: () => formatNightlySummary,
12797
- formatProposalCard: () => formatProposalCard
12798
- });
12799
- function pct(ratio) {
12800
- return `${Math.round(ratio * 100)}%`;
12801
- }
12802
- function formatProposalCard(params) {
12803
- const { category, insight, why, targetFile, confidence, proposedAction, proposedDiff } = params;
12804
- const header2 = `[${category}] ${insight}`;
12805
- const whyLine = why ? `Why: ${why}` : null;
12806
- const confidencePct = pct(confidence);
12807
- const showTarget = category !== "codebase" && targetFile && targetFile !== "codebase";
12808
- const targetLine = showTarget ? `Target: ${targetFile} | Confidence: ${confidencePct}` : `Confidence: ${confidencePct}`;
12809
- const lines = [header2, ""];
12810
- if (whyLine) lines.push(whyLine);
12811
- lines.push(targetLine);
12812
- if (proposedDiff) {
12813
- const actionLabel = proposedAction ? ` (${proposedAction})` : "";
12814
- lines.push("", `Proposed change${actionLabel}:`, "```diff", proposedDiff, "```");
12815
- }
12816
- return lines.join("\n");
12817
- }
12818
- function formatNightlySummary(insights) {
12819
- const count = insights.length;
12820
- const header2 = `Nightly Reflection \u2014 ${count} proposal${count === 1 ? "" : "s"} ready`;
12821
- const list = insights.map((ins, i) => `${i + 1}. [${ins.category}] ${ins.insight}`).join("\n");
12822
- return `${header2}
12823
-
12824
- ${list}
12825
-
12826
- Review with /evolve`;
12827
- }
12828
- function formatGrowthReport(metrics, modelPerformance) {
12829
- const { correctionsBefore, correctionsAfter, praiseRatio, insightsApplied, pendingCount, topInsight } = metrics;
12830
- const reduction = correctionsBefore > 0 ? Math.round((correctionsBefore - correctionsAfter) / correctionsBefore * 100) : 0;
12831
- const lines = [];
12832
- lines.push("Growth Report");
12833
- lines.push("");
12834
- lines.push(`Corrections: ${correctionsBefore.toFixed(1)} -> ${correctionsAfter.toFixed(1)} (${reduction}% reduction)`);
12835
- lines.push(`Praise ratio: ${praiseRatio.toFixed(1)}x`);
12836
- lines.push(`Insights applied: ${insightsApplied} | Pending: ${pendingCount}`);
12837
- if (topInsight) {
12838
- lines.push(`Top insight: "${topInsight.insight}" (${pct(topInsight.effectiveness)} effective)`);
12839
- }
12840
- if (modelPerformance.length > 0) {
12841
- lines.push("");
12842
- lines.push("Model Performance:");
12843
- for (const row of modelPerformance) {
12844
- const total = row.praises + row.corrections + row.errors;
12845
- const scoreLabel = total > 0 ? ` (${row.praises}P / ${row.corrections}C / ${row.errors}E)` : "";
12846
- lines.push(` ${row.backend} ${row.model} [${row.thinking}]${scoreLabel}`);
12847
- }
12848
- }
12849
- return lines.join("\n");
12850
- }
12851
- function formatDiffCodeBlock(diff) {
12852
- return `\`\`\`diff
12853
- ${diff}
12854
- \`\`\``;
12855
- }
12856
- function buildProposalKeyboard(insightId, category) {
12857
- if (category === "codebase") {
12858
- return [
12859
- [
12860
- { label: "Show Diff", data: `evolve:diff:${insightId}`, style: "primary" },
12861
- { label: "Discuss", data: `evolve:discuss:${insightId}` },
12862
- { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
12863
- ]
12864
- ];
12865
- }
12866
- return [
12867
- [
12868
- { label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
12869
- { label: "Discuss", data: `evolve:discuss:${insightId}` },
12870
- { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
12871
- ]
12872
- ];
12873
- }
12874
- function buildEvolveOnboardingKeyboard() {
12875
- return [
12876
- [{ label: "\u2705 Enable Self-Learning", data: "evolve:toggle", style: "success" }],
12877
- [{ label: "One-Time Analysis", data: "evolve:analyze" }]
12878
- ];
12879
- }
12880
- function buildEvolveMenuKeyboard(ctx) {
12881
- const reviewLabel = ctx.pendingProposals > 0 ? `Review (${ctx.pendingProposals})` : "Review";
12882
- return [
12883
- [
12884
- { label: "\u{1F50D} Analyze Now", data: "evolve:analyze", style: "success" },
12885
- ctx.pendingProposals > 0 ? { label: reviewLabel, data: "evolve:review", style: "primary" } : { label: reviewLabel, data: "evolve:review" }
12886
- ],
12887
- [
12888
- { label: "Stats", data: "evolve:stats" },
12889
- { label: "History", data: "evolve:history" }
12890
- ],
12891
- [
12892
- { label: "Model", data: "evolve:model" },
12893
- { label: "Undo", data: "evolve:undo", style: "danger" },
12894
- { label: "\u23F8 Disable", data: "evolve:toggle", style: "danger" }
12895
- ]
12896
- ];
12897
- }
12898
- function buildUndoKeyboard(insights) {
12899
- return insights.map((ins) => [
12900
- { label: `Undo #${ins.id}: ${ins.insight}`, data: `evolve:undo:${ins.id}`, style: "danger" }
12901
- ]);
12902
- }
12903
- function buildModelKeyboard(currentMode) {
12904
- const modes = [
12905
- { label: "Auto", mode: "auto" },
12906
- { label: "Pinned", mode: "pinned" },
12907
- { label: "Cheap", mode: "cheap" }
12908
- ];
12909
- return [
12910
- modes.map(({ label: label2, mode }) => ({
12911
- label: mode === currentMode ? `[${label2}]` : label2,
12912
- data: `evolve:model:${mode}`,
12913
- ...mode === currentMode ? { style: "primary" } : {}
12914
- }))
12915
- ];
12916
- }
12917
- var init_propose = __esm({
12918
- "src/reflection/propose.ts"() {
12919
- "use strict";
12920
- }
12921
- });
12922
-
12923
12979
  // src/router.ts
12924
12980
  import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir2, readdir as readdir3, stat } from "fs/promises";
12925
12981
  import { existsSync as existsSync19 } from "fs";
@@ -14835,32 +14891,49 @@ Message: "${testMsg}"`, { parseMode: "plain" });
14835
14891
  break;
14836
14892
  }
14837
14893
  case "reflect": {
14838
- const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
14839
- const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14840
- 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" });
14841
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));
14842
14919
  const insights = await runAnalysis2(chatId, { force: true });
14843
14920
  if (insights.length === 0) {
14844
- const { getReflectionStatus: getRefStatus } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14845
- const isFrozen = getRefStatus(getDb(), chatId) === "frozen";
14846
- if (isFrozen) {
14847
- const msg2 = "No insights found. Self-learning is disabled \u2014\nenable it with /evolve so feedback signals are\ncaptured automatically between analyses.";
14848
- if (typeof channel.sendKeyboard === "function") {
14849
- await channel.sendKeyboard(chatId, msg2, [[
14850
- { label: "Open /evolve", data: "evolve:menu", style: "primary" }
14851
- ]]);
14852
- } else {
14853
- await channel.sendText(chatId, msg2, { parseMode: "plain" });
14854
- }
14855
- } else {
14856
- await channel.sendText(chatId, "No new insights from recent interactions.", { parseMode: "plain" });
14857
- }
14921
+ await channel.sendText(chatId, "Step 3/3: No actionable improvements found in this session.", { parseMode: "plain" });
14858
14922
  } else {
14859
- const items = insights.map((ins, i) => ({ id: i + 1, category: ins.category, insight: ins.insight }));
14860
- 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
+ }
14861
14934
  }
14862
14935
  } catch (e) {
14863
- 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" });
14864
14937
  }
14865
14938
  break;
14866
14939
  }
@@ -15314,7 +15387,8 @@ async function handleSideQuest(parentChatId, msg, channel) {
15314
15387
  backendId: adapter.id,
15315
15388
  model: model2 ?? adapter.defaultModel
15316
15389
  });
15317
- } catch {
15390
+ } catch (e) {
15391
+ log(`[reflection] Side quest signal detection error: ${e}`);
15318
15392
  }
15319
15393
  } catch (err) {
15320
15394
  typingActive = false;
@@ -15875,7 +15949,7 @@ async function sendJobDetail(chatId, jobId, channel) {
15875
15949
  const lines = [
15876
15950
  `Job #${job.id}: ${job.title ?? job.description}`,
15877
15951
  buildSectionHeader("", 22),
15878
- ...job.title ? [`Task: ${job.description.length > 80 ? job.description.slice(0, 77) + "\u2026" : job.description}`] : [],
15952
+ ...job.title ? [`Task: ${job.description}`] : [],
15879
15953
  `Runs: ${schedule2}${tz}`,
15880
15954
  `Backend: ${backend2} | Model: ${model2}`,
15881
15955
  `Last run: ${lastRun}${lastRunStatus ? ` (${lastRunStatus})` : ""}`,
@@ -16242,9 +16316,12 @@ ${PERM_MODES[chosen]}`,
16242
16316
  const pending2 = pendingInterrupts.get(targetChatId);
16243
16317
  if (pending2) {
16244
16318
  pendingInterrupts.delete(targetChatId);
16245
- handleSideQuest(targetChatId, pending2.msg, pending2.channel).catch(
16246
- (err) => error(`[router] Side quest error for ${targetChatId}:`, err)
16247
- );
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
+ });
16248
16325
  } else {
16249
16326
  await channel.sendText(chatId, "Main task finished \u2014 your message was already processed.", { parseMode: "plain" });
16250
16327
  }
@@ -17454,7 +17531,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
17454
17531
  const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
17455
17532
  await channel.sendKeyboard(chatId, header2, buttons);
17456
17533
  }
17457
- 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;
17458
17535
  var init_router = __esm({
17459
17536
  "src/router.ts"() {
17460
17537
  "use strict";
@@ -17597,6 +17674,8 @@ var init_router = __esm({
17597
17674
  MAX_SIDE_QUESTS = 2;
17598
17675
  pendingFallbackMessages = /* @__PURE__ */ new Map();
17599
17676
  dashboardClawWarnings = /* @__PURE__ */ new Map();
17677
+ lastReflectTime = /* @__PURE__ */ new Map();
17678
+ REFLECT_COOLDOWN_MS = 12e4;
17600
17679
  pendingSummaryUndo = /* @__PURE__ */ new Map();
17601
17680
  pendingNewchatUndo = /* @__PURE__ */ new Map();
17602
17681
  CLI_INSTALL_HINTS = {
@@ -18611,6 +18690,8 @@ async function main() {
18611
18690
  const shutdown = async (signal) => {
18612
18691
  log(`[cc-claw] Received ${signal}, shutting down...`);
18613
18692
  try {
18693
+ const { stopAllActiveAgents: stopAllActiveAgents2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
18694
+ stopAllActiveAgents2();
18614
18695
  stopAllHeartbeats();
18615
18696
  stopHealthMonitor3();
18616
18697
  stopMonitor();
@@ -19378,14 +19459,18 @@ async function doctorCommand(globalOpts, localOpts) {
19378
19459
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
19379
19460
  }
19380
19461
  const CLI_BINARIES = { claude: "claude", gemini: "gemini", codex: "codex", cursor: "agent" };
19462
+ let installedBackends = 0;
19381
19463
  for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
19382
19464
  try {
19383
19465
  const path = execFileSync3("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
19384
19466
  checks.push({ name: `${label2} CLI`, status: "ok", message: path });
19467
+ installedBackends++;
19385
19468
  } catch {
19386
- checks.push({ name: `${label2} CLI`, status: "warning", message: "not found in PATH" });
19387
19469
  }
19388
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
+ }
19389
19474
  try {
19390
19475
  const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
19391
19476
  const running = await isDaemonRunning2();
@@ -19415,6 +19500,13 @@ async function doctorCommand(globalOpts, localOpts) {
19415
19500
  } catch {
19416
19501
  checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
19417
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
+ }
19418
19510
  const tokenPath = `${DATA_PATH}/api-token`;
19419
19511
  if (existsSync26(tokenPath)) {
19420
19512
  try {
@@ -23330,6 +23422,20 @@ program.command("setup").description("Interactive configuration wizard").action(
23330
23422
  await Promise.resolve().then(() => (init_setup(), setup_exports));
23331
23423
  });
23332
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
+ }
23333
23439
  if (argv.includes("--ai")) {
23334
23440
  const { generateAiSkill: generateAiSkill2, installAiSkill: installAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
23335
23441
  if (argv.includes("--install")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.11.2",
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",