cc-claw 0.10.1 → 0.11.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 +909 -432
  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.10.1" : (() => {
75
+ VERSION = true ? "0.11.1" : (() => {
76
76
  try {
77
77
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
78
78
  } catch {
@@ -703,6 +703,7 @@ __export(store_exports4, {
703
703
  setReflectionModelConfig: () => setReflectionModelConfig,
704
704
  setReflectionStatus: () => setReflectionStatus,
705
705
  updateInsightEffectiveness: () => updateInsightEffectiveness,
706
+ updateInsightProposal: () => updateInsightProposal,
706
707
  updateInsightRollback: () => updateInsightRollback,
707
708
  updateInsightStatus: () => updateInsightStatus,
708
709
  upsertGrowthMetric: () => upsertGrowthMetric
@@ -932,6 +933,11 @@ function updateInsightStatus(db3, id, status) {
932
933
  db3.prepare("UPDATE insights SET status = ? WHERE id = ?").run(status, id);
933
934
  }
934
935
  }
936
+ function updateInsightProposal(db3, id, targetFile, proposedDiff, proposedAction) {
937
+ db3.prepare(`
938
+ UPDATE insights SET targetFile = ?, proposedDiff = ?, proposedAction = ? WHERE id = ?
939
+ `).run(targetFile, proposedDiff ?? null, proposedAction ?? null, id);
940
+ }
935
941
  function updateInsightRollback(db3, id, rollbackData) {
936
942
  db3.prepare("UPDATE insights SET rollbackData = ? WHERE id = ?").run(rollbackData, id);
937
943
  }
@@ -4556,7 +4562,7 @@ function searchContext(userMessage) {
4556
4562
  }
4557
4563
  return null;
4558
4564
  }
4559
- async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permMode, responseStyle, agentMode) {
4565
+ async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permMode, responseStyle, agentMode, sideQuestContext) {
4560
4566
  const sections = [];
4561
4567
  if (Date.now() - lastSyncMs >= 5e3) {
4562
4568
  syncNativeCliFiles();
@@ -4580,9 +4586,12 @@ ${ctx}`);
4580
4586
  }
4581
4587
  }
4582
4588
  if (chatId && tier !== "slim" && tier !== "chat") {
4583
- const bridge = consumeContextBridge(chatId);
4584
- if (bridge) {
4585
- sections.push(bridge);
4589
+ if (sideQuestContext) {
4590
+ const bridge = buildContextBridge(sideQuestContext.parentChatId, 15);
4591
+ if (bridge) sections.push(bridge);
4592
+ } else {
4593
+ const bridge = consumeContextBridge(chatId);
4594
+ if (bridge) sections.push(bridge);
4586
4595
  }
4587
4596
  }
4588
4597
  if (tier !== "slim") {
@@ -4620,6 +4629,23 @@ ${ctx}`);
4620
4629
  "[React] Start your response with [REACT:emoji] \u2014 one emoji from the Telegram allowed set matching the message tone. Examples: \u{1F914} question, \u{1FAE1} task/request, \u{1F525} exciting, \u{1F923} funny, \u{1F622} sad, \u{1F4AF} agree, \u{1F389} celebrate, \u{1F468}\u200D\u{1F4BB} coding, \u{1F92F} mind-blown, \u{1F440} interesting, \u{1F91D} thanks, \u{1F60E} chill."
4621
4630
  );
4622
4631
  }
4632
+ if (sideQuestContext) {
4633
+ const { getInFlightMessage: getInFlightMessage3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
4634
+ const inFlightMsg = getInFlightMessage3(sideQuestContext.parentChatId);
4635
+ const preamble = [
4636
+ `[Side quest context]`,
4637
+ `You are handling a side request while the user's main agent works on another task.`
4638
+ ];
4639
+ if (inFlightMsg) {
4640
+ preamble.push(`The main agent is currently working on: "${inFlightMsg}"`);
4641
+ }
4642
+ preamble.push(
4643
+ `The user's recent conversation history is included above for context.`,
4644
+ `Focus specifically on what the user is asking in THIS message.`,
4645
+ `[End side quest context]`
4646
+ );
4647
+ sections.push(preamble.join("\n"));
4648
+ }
4623
4649
  sections.push(userMessage);
4624
4650
  const result = sections.join("\n\n");
4625
4651
  log(`[bootstrap] Assembled prompt: tier=${tier}, sections=${sections.length}, totalChars=${result.length}`);
@@ -6772,19 +6798,26 @@ async function spawnAnalysis(adapter, model2, prompt) {
6772
6798
  if (!resultText) resultText = accumulatedText;
6773
6799
  return resultText;
6774
6800
  }
6775
- async function runAnalysis(chatId) {
6801
+ async function runAnalysis(chatId, opts = {}) {
6776
6802
  const db3 = getDb();
6777
- const status = getReflectionStatus(db3, chatId);
6778
- if (status === "frozen") {
6779
- log(`[reflection] Skipping analysis for ${chatId} \u2014 reflection is frozen`);
6780
- return [];
6803
+ const { force = false } = opts;
6804
+ if (!force) {
6805
+ const status = getReflectionStatus(db3, chatId);
6806
+ if (status === "frozen") {
6807
+ log(`[reflection] Skipping analysis for ${chatId} \u2014 reflection is frozen`);
6808
+ return [];
6809
+ }
6781
6810
  }
6782
6811
  const signals = getUnprocessedSignals(db3, chatId);
6783
- if (signals.length === 0) {
6812
+ if (!force && signals.length === 0) {
6784
6813
  log(`[reflection] No unprocessed signals for ${chatId}`);
6785
6814
  return [];
6786
6815
  }
6787
6816
  const conversations = getTodayConversations(chatId);
6817
+ if (signals.length === 0 && !conversations) {
6818
+ log(`[reflection] No signals and no conversations for ${chatId} \u2014 nothing to analyze`);
6819
+ return [];
6820
+ }
6788
6821
  const soulMd = readIdentityFile("SOUL.md");
6789
6822
  const userMd = readIdentityFile("USER.md");
6790
6823
  const applied = getAppliedInsights(db3, chatId);
@@ -6819,7 +6852,7 @@ async function runAnalysis(chatId) {
6819
6852
  return [];
6820
6853
  }
6821
6854
  const { adapter, model: model2 } = resolved;
6822
- log(`[reflection] Running analysis via ${adapter.id}:${model2} for chat ${chatId} (${signals.length} signals)`);
6855
+ log(`[reflection] Running analysis via ${adapter.id}:${model2} for chat ${chatId} (${signals.length} signals, force=${force})`);
6823
6856
  let rawOutput;
6824
6857
  try {
6825
6858
  rawOutput = await spawnAnalysis(adapter, model2, prompt);
@@ -6829,13 +6862,12 @@ async function runAnalysis(chatId) {
6829
6862
  }
6830
6863
  if (!rawOutput || rawOutput.includes("NO_INSIGHTS")) {
6831
6864
  log(`[reflection] Analysis returned no insights for ${chatId}`);
6832
- markSignalsProcessed(db3, signals.map((s) => s.id));
6865
+ if (signals.length > 0) markSignalsProcessed(db3, signals.map((s) => s.id));
6833
6866
  return [];
6834
6867
  }
6835
6868
  const parsed = parseAnalysisOutput(rawOutput);
6836
- const signalIdStr = signals.map((s) => s.id).join(",");
6869
+ const signalIdStr = signals.length > 0 ? signals.map((s) => s.id).join(",") : "manual";
6837
6870
  for (const insight of parsed) {
6838
- const insightStatus = insight.category === "codebase" ? "recommendation" : "pending";
6839
6871
  let conflictsWithId = null;
6840
6872
  if (insight.conflictsWith && insight.conflictsWith !== "none") {
6841
6873
  const parsed_id = parseInt(insight.conflictsWith, 10);
@@ -6856,19 +6888,20 @@ async function runAnalysis(chatId) {
6856
6888
  model: model2
6857
6889
  });
6858
6890
  }
6859
- markSignalsProcessed(db3, signals.map((s) => s.id));
6891
+ if (signals.length > 0) markSignalsProcessed(db3, signals.map((s) => s.id));
6860
6892
  aggregateDailyMetrics(db3, chatId);
6861
6893
  logActivity(db3, {
6862
6894
  chatId,
6863
6895
  source: "agent",
6864
6896
  eventType: "reflection_triggered",
6865
- summary: `Reflection analysis produced ${parsed.length} insight(s) from ${signals.length} signal(s)`,
6897
+ summary: `Reflection analysis produced ${parsed.length} insight(s) from ${signals.length} signal(s)${force ? " (manual)" : ""}`,
6866
6898
  detail: {
6867
6899
  signalCount: signals.length,
6868
6900
  insightCount: parsed.length,
6869
6901
  backend: adapter.id,
6870
6902
  model: model2,
6871
- categories: parsed.map((p) => p.category)
6903
+ categories: parsed.map((p) => p.category),
6904
+ forced: force
6872
6905
  }
6873
6906
  });
6874
6907
  log(`[reflection] Analysis complete for ${chatId}: ${parsed.length} insight(s) from ${signals.length} signal(s)`);
@@ -8446,11 +8479,19 @@ var init_detect = __esm({
8446
8479
  var agent_exports = {};
8447
8480
  __export(agent_exports, {
8448
8481
  askAgent: () => askAgent,
8482
+ getInFlightMessage: () => getInFlightMessage,
8449
8483
  isChatBusy: () => isChatBusy,
8484
+ isSyntheticChatId: () => isSyntheticChatId,
8450
8485
  stopAgent: () => stopAgent
8451
8486
  });
8452
8487
  import { spawn as spawn5 } from "child_process";
8453
8488
  import { createInterface as createInterface4 } from "readline";
8489
+ function isSyntheticChatId(chatId) {
8490
+ return chatId.startsWith("sq:") || chatId.startsWith("cron:");
8491
+ }
8492
+ function getInFlightMessage(chatId) {
8493
+ return activeChats.get(chatId)?.userMessage;
8494
+ }
8454
8495
  function killProcessGroup(proc, signal = "SIGTERM") {
8455
8496
  try {
8456
8497
  if (proc.pid) process.kill(-proc.pid, signal);
@@ -8650,12 +8691,16 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
8650
8691
  });
8651
8692
  });
8652
8693
  }
8653
- async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation) {
8694
+ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation, parentChatId) {
8654
8695
  const geminiAdapter = adapter;
8655
8696
  const slots = getEligibleGeminiSlots(rotationMode);
8656
8697
  if (slots.length === 0) {
8657
8698
  return spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, opts);
8658
8699
  }
8700
+ if (parentChatId) {
8701
+ const { env } = geminiAdapter.getEnvForSlot(parentChatId, void 0, rotationMode);
8702
+ return await spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
8703
+ }
8659
8704
  const maxAttempts = Math.min(slots.length, 10);
8660
8705
  let lastError;
8661
8706
  for (let i = 0; i < maxAttempts; i++) {
@@ -8716,17 +8761,19 @@ function askAgent(chatId, userMessage, opts) {
8716
8761
  return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
8717
8762
  }
8718
8763
  async function askAgentImpl(chatId, userMessage, opts) {
8719
- const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns, onSlotRotation, agentMode: optsAgentMode, onSubagentActivity } = opts ?? {};
8720
- const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(chatId);
8721
- const mode = permMode ?? getMode(chatId);
8722
- const responseStyle = getResponseStyle(chatId);
8723
- const thinkingLevel = getThinkingLevel(chatId);
8764
+ const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns, onSlotRotation, agentMode: optsAgentMode, onSubagentActivity, settingsSourceChatId } = opts ?? {};
8765
+ const settingsChat = settingsSourceChatId ?? chatId;
8766
+ const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(settingsChat);
8767
+ const mode = permMode ?? getMode(settingsChat);
8768
+ const responseStyle = getResponseStyle(settingsChat);
8769
+ const thinkingLevel = getThinkingLevel(settingsChat);
8724
8770
  const resolvedCwd = cwd ?? WORKSPACE_PATH;
8725
8771
  const tier = bootstrapTier ?? "full";
8726
- const effectiveAgentMode = optsAgentMode ?? getAgentMode(chatId);
8727
- const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, chatId, mode, responseStyle, effectiveAgentMode);
8728
- const existingSessionId = getSessionId(chatId);
8729
- const allowedTools = getEnabledTools(chatId);
8772
+ const effectiveAgentMode = optsAgentMode ?? getAgentMode(settingsChat);
8773
+ const sideQuestCtx = settingsSourceChatId ? { parentChatId: settingsSourceChatId, actualChatId: chatId } : void 0;
8774
+ const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx);
8775
+ const existingSessionId = getSessionId(settingsChat);
8776
+ const allowedTools = getEnabledTools(settingsChat);
8730
8777
  const mcpConfigPath = tier !== "slim" && effectiveAgentMode !== "native" && MCP_CONFIG_FLAG[adapter.id] ? getMcpConfigPath(chatId) : null;
8731
8778
  const baseConfig = adapter.buildSpawnConfig({
8732
8779
  prompt: fullPrompt,
@@ -8756,7 +8803,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
8756
8803
  }
8757
8804
  return cfg;
8758
8805
  })() : baseConfig;
8759
- const cancelState = { cancelled: false };
8806
+ const cancelState = { cancelled: false, userMessage };
8760
8807
  activeChats.set(chatId, cancelState);
8761
8808
  const spawnOpts = { onStream, onToolAction, onSubagentActivity };
8762
8809
  const resolvedModel = model2 ?? adapter.defaultModel;
@@ -8766,7 +8813,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
8766
8813
  try {
8767
8814
  if (useGeminiRotation) {
8768
8815
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
8769
- result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb);
8816
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId);
8770
8817
  } else {
8771
8818
  result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
8772
8819
  }
@@ -8777,7 +8824,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
8777
8824
  clearSession(chatId);
8778
8825
  if (useGeminiRotation) {
8779
8826
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
8780
- result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb);
8827
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId);
8781
8828
  } else {
8782
8829
  result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
8783
8830
  }
@@ -8802,7 +8849,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
8802
8849
  if (cancelState.cancelled) {
8803
8850
  return { text: "Stopped.", usage: { input: result.input, output: result.output, cacheRead: result.cacheRead } };
8804
8851
  }
8805
- if (result.sessionId) {
8852
+ if (result.sessionId && !isSyntheticChatId(chatId)) {
8806
8853
  setSessionId(chatId, result.sessionId);
8807
8854
  }
8808
8855
  if (!result.resultText && result.sawToolEvents) {
@@ -8813,7 +8860,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
8813
8860
  await summarizeSession(chatId);
8814
8861
  clearSession(chatId);
8815
8862
  }
8816
- if (result.resultText) {
8863
+ if (result.resultText && !isSyntheticChatId(chatId)) {
8817
8864
  appendToLog(chatId, userMessage, result.resultText, adapter.id, model2 ?? null, result.sessionId ?? null);
8818
8865
  const AUTO_SUMMARIZE_THRESHOLD = 30;
8819
8866
  const pairCount = tier !== "chat" ? getMessagePairCount(chatId) : 0;
@@ -8944,7 +8991,7 @@ ${responseText.slice(0, 500)}`);
8944
8991
  if (!cleanText) return true;
8945
8992
  if (channelName === "telegram") {
8946
8993
  const parsed = parseTelegramTarget(targetChatId);
8947
- await channel.sendText(parsed.chatId, cleanText, void 0, parsed.threadId);
8994
+ await channel.sendText(parsed.chatId, cleanText, { threadId: parsed.threadId });
8948
8995
  } else {
8949
8996
  await channel.sendText(targetChatId, cleanText);
8950
8997
  }
@@ -8990,7 +9037,7 @@ async function notifyJobFailure(job, errorMessage2, retryInfo) {
8990
9037
  const lines = [`Scheduled job #${job.id} failed: ${errorMessage2}`];
8991
9038
  if (retryInfo) lines.push(retryInfo);
8992
9039
  try {
8993
- await channel.sendText(notifyTarget, lines.join("\n"), "plain");
9040
+ await channel.sendText(notifyTarget, lines.join("\n"), { parseMode: "plain" });
8994
9041
  } catch {
8995
9042
  }
8996
9043
  }
@@ -9002,7 +9049,7 @@ async function notifyJobAutoPaused(job) {
9002
9049
  await channel.sendText(
9003
9050
  job.chatId,
9004
9051
  `Job #${job.id} ("${job.description}") has been auto-paused after ${job.consecutiveFailures} consecutive failures. Use /resume ${job.id} to re-enable.`,
9005
- "plain"
9052
+ { parseMode: "plain" }
9006
9053
  );
9007
9054
  } catch {
9008
9055
  }
@@ -10022,6 +10069,9 @@ var init_telegram = __esm({
10022
10069
  // src/channels/telegram.ts
10023
10070
  import { Bot, InlineKeyboard, InputFile } from "grammy";
10024
10071
  function numericChatId(chatId) {
10072
+ if (chatId.startsWith("sq:") || chatId.startsWith("cron:")) {
10073
+ throw new Error(`Synthetic chatId "${chatId}" passed to Telegram API`);
10074
+ }
10025
10075
  const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
10026
10076
  return parseInt(raw);
10027
10077
  }
@@ -10156,7 +10206,7 @@ var init_telegram2 = __esm({
10156
10206
  error("[telegram] Handler error:", err);
10157
10207
  });
10158
10208
  } else {
10159
- this.sendText(chatId, "I can handle text, voice, photos, documents, and videos. This message type isn't supported yet.", "plain").catch(() => {
10209
+ this.sendText(chatId, "I can handle text, voice, photos, documents, and videos. This message type isn't supported yet.", { parseMode: "plain" }).catch(() => {
10160
10210
  });
10161
10211
  }
10162
10212
  });
@@ -10219,12 +10269,14 @@ var init_telegram2 = __esm({
10219
10269
  async sendTyping(chatId) {
10220
10270
  await this.bot.api.sendChatAction(numericChatId(chatId), "typing");
10221
10271
  }
10222
- async sendText(chatId, text, parseMode, threadId) {
10272
+ async sendText(chatId, text, opts) {
10273
+ const { parseMode, threadId, replyToMessageId } = opts ?? {};
10223
10274
  const threadOpts = threadId ? { message_thread_id: threadId } : {};
10275
+ const replyOpts = replyToMessageId ? { reply_parameters: { message_id: replyToMessageId } } : {};
10224
10276
  if (parseMode === "plain") {
10225
10277
  const plainChunks = splitMessage(text);
10226
10278
  for (const chunk of plainChunks) {
10227
- const sent = await this.bot.api.sendMessage(numericChatId(chatId), chunk, { ...threadOpts });
10279
+ const sent = await this.bot.api.sendMessage(numericChatId(chatId), chunk, { ...threadOpts, ...replyOpts });
10228
10280
  this.trackAgentMessage(sent.message_id, chatId);
10229
10281
  }
10230
10282
  return;
@@ -10235,14 +10287,15 @@ var init_telegram2 = __esm({
10235
10287
  try {
10236
10288
  const sent = await this.bot.api.sendMessage(numericChatId(chatId), chunk, {
10237
10289
  parse_mode: "HTML",
10238
- ...threadOpts
10290
+ ...threadOpts,
10291
+ ...replyOpts
10239
10292
  });
10240
10293
  this.trackAgentMessage(sent.message_id, chatId);
10241
10294
  } catch {
10242
10295
  const sent = await this.bot.api.sendMessage(
10243
10296
  numericChatId(chatId),
10244
10297
  chunk.replace(/<[^>]+>/g, ""),
10245
- { ...threadOpts }
10298
+ { ...threadOpts, ...replyOpts }
10246
10299
  );
10247
10300
  this.trackAgentMessage(sent.message_id, chatId);
10248
10301
  }
@@ -10818,7 +10871,7 @@ function hasActiveProfile(chatId) {
10818
10871
  }
10819
10872
  async function startProfileWizard(chatId, channel) {
10820
10873
  activeProfiles.set(chatId, { step: "name" });
10821
- await channel.sendText(chatId, "Let's set up your profile! I'll ask a few quick questions.\n\nWhat's your name?", "plain");
10874
+ await channel.sendText(chatId, "Let's set up your profile! I'll ask a few quick questions.\n\nWhat's your name?", { parseMode: "plain" });
10822
10875
  }
10823
10876
  async function handleProfileText(chatId, text, channel) {
10824
10877
  const state = activeProfiles.get(chatId);
@@ -10833,9 +10886,9 @@ async function handleProfileText(chatId, text, channel) {
10833
10886
  [{ label: "UTC", data: "profile:tz:UTC" }, { label: "Israel", data: "profile:tz:Asia/Jerusalem" }],
10834
10887
  [{ label: "Europe/London", data: "profile:tz:Europe/London" }, { label: "Asia/Tokyo", data: "profile:tz:Asia/Tokyo" }]
10835
10888
  ]);
10836
- await channel.sendText(chatId, "Or type your timezone (e.g. America/Chicago):", "plain");
10889
+ await channel.sendText(chatId, "Or type your timezone (e.g. America/Chicago):", { parseMode: "plain" });
10837
10890
  } else {
10838
- await channel.sendText(chatId, `Hi ${state.name}! What's your timezone? (e.g. America/New_York, UTC, Asia/Jerusalem)`, "plain");
10891
+ await channel.sendText(chatId, `Hi ${state.name}! What's your timezone? (e.g. America/New_York, UTC, Asia/Jerusalem)`, { parseMode: "plain" });
10839
10892
  }
10840
10893
  break;
10841
10894
  case "timezone":
@@ -10843,7 +10896,7 @@ async function handleProfileText(chatId, text, channel) {
10843
10896
  Intl.DateTimeFormat(void 0, { timeZone: text.trim() });
10844
10897
  state.timezone = text.trim();
10845
10898
  } catch {
10846
- await channel.sendText(chatId, "Invalid timezone. Try again (e.g. America/New_York):", "plain");
10899
+ await channel.sendText(chatId, "Invalid timezone. Try again (e.g. America/New_York):", { parseMode: "plain" });
10847
10900
  return;
10848
10901
  }
10849
10902
  state.step = "style";
@@ -10853,7 +10906,7 @@ async function handleProfileText(chatId, text, channel) {
10853
10906
  [{ label: "Casual", data: "profile:style:casual" }, { label: "Formal", data: "profile:style:formal" }]
10854
10907
  ]);
10855
10908
  } else {
10856
- await channel.sendText(chatId, "Communication style? (concise / detailed / casual / formal)", "plain");
10909
+ await channel.sendText(chatId, "Communication style? (concise / detailed / casual / formal)", { parseMode: "plain" });
10857
10910
  }
10858
10911
  break;
10859
10912
  case "style":
@@ -10865,7 +10918,7 @@ async function handleProfileText(chatId, text, channel) {
10865
10918
  [{ label: "Personal assistant", data: "profile:use:personal" }, { label: "All of the above", data: "profile:use:all" }]
10866
10919
  ]);
10867
10920
  } else {
10868
- await channel.sendText(chatId, "Primary use? (coding / research / personal / all)", "plain");
10921
+ await channel.sendText(chatId, "Primary use? (coding / research / personal / all)", { parseMode: "plain" });
10869
10922
  }
10870
10923
  break;
10871
10924
  case "use_case":
@@ -10880,14 +10933,14 @@ async function handleProfileCallback(chatId, data, channel) {
10880
10933
  if (data.startsWith("profile:tz:")) {
10881
10934
  state.timezone = data.slice(11);
10882
10935
  state.step = "style";
10883
- await channel.sendText(chatId, `Timezone: ${state.timezone}`, "plain");
10936
+ await channel.sendText(chatId, `Timezone: ${state.timezone}`, { parseMode: "plain" });
10884
10937
  if (typeof channel.sendKeyboard === "function") {
10885
10938
  await channel.sendKeyboard(chatId, "How should I communicate?", [
10886
10939
  [{ label: "Concise", data: "profile:style:concise" }, { label: "Detailed", data: "profile:style:detailed" }],
10887
10940
  [{ label: "Casual", data: "profile:style:casual" }, { label: "Formal", data: "profile:style:formal" }]
10888
10941
  ]);
10889
10942
  } else {
10890
- await channel.sendText(chatId, "Communication style? (concise / detailed / casual / formal)", "plain");
10943
+ await channel.sendText(chatId, "Communication style? (concise / detailed / casual / formal)", { parseMode: "plain" });
10891
10944
  }
10892
10945
  } else if (data.startsWith("profile:style:")) {
10893
10946
  state.style = data.slice(14);
@@ -10928,7 +10981,7 @@ Style: ${state.style}
10928
10981
  Use: ${state.useCase}
10929
10982
 
10930
10983
  You can edit ~/.cc-claw/identity/USER.md anytime or run /setup-profile again.`,
10931
- "plain"
10984
+ { parseMode: "plain" }
10932
10985
  );
10933
10986
  }
10934
10987
  function extractUserUpdates(text) {
@@ -11743,9 +11796,9 @@ async function startWizard(chatId, input, channel) {
11743
11796
  lines.push(' "every Monday at 10:30am"');
11744
11797
  lines.push(' "every 5 minutes"');
11745
11798
  lines.push(' or a raw cron: "0 9 * * 1-5"');
11746
- await channel.sendText(chatId, lines.join("\n"), "plain");
11799
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
11747
11800
  } else {
11748
- await channel.sendText(chatId, lines.join("\n"), "plain");
11801
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
11749
11802
  await promptTimezone(chatId, channel);
11750
11803
  }
11751
11804
  }
@@ -11758,7 +11811,7 @@ async function handleWizardText(chatId, text, channel) {
11758
11811
  const trimmed = text.trim();
11759
11812
  if (trimmed.toLowerCase() === "keep" && pending.cron) {
11760
11813
  pending.step = "timezone";
11761
- await channel.sendText(chatId, `Keeping schedule: ${pending.cron}`, "plain");
11814
+ await channel.sendText(chatId, `Keeping schedule: ${pending.cron}`, { parseMode: "plain" });
11762
11815
  await promptTimezone(chatId, channel);
11763
11816
  return;
11764
11817
  }
@@ -11766,7 +11819,7 @@ async function handleWizardText(chatId, text, channel) {
11766
11819
  pending.cron = trimmed;
11767
11820
  pending.scheduleType = "cron";
11768
11821
  pending.step = "timezone";
11769
- await channel.sendText(chatId, `Schedule set: ${trimmed}`, "plain");
11822
+ await channel.sendText(chatId, `Schedule set: ${trimmed}`, { parseMode: "plain" });
11770
11823
  await promptTimezone(chatId, channel);
11771
11824
  return;
11772
11825
  }
@@ -11776,13 +11829,13 @@ async function handleWizardText(chatId, text, channel) {
11776
11829
  pending.scheduleType = "cron";
11777
11830
  if (parsed.task && parsed.task !== text) pending.task = parsed.task;
11778
11831
  pending.step = "timezone";
11779
- await channel.sendText(chatId, `Schedule: ${pending.cron}`, "plain");
11832
+ await channel.sendText(chatId, `Schedule: ${pending.cron}`, { parseMode: "plain" });
11780
11833
  await promptTimezone(chatId, channel);
11781
11834
  } else {
11782
11835
  await channel.sendText(chatId, `Couldn't parse that schedule. Try something like:
11783
11836
  "every day at 9am"
11784
11837
  "every 5 minutes"
11785
- or a raw cron expression like "0 9 * * 1-5"`, "plain");
11838
+ or a raw cron expression like "0 9 * * 1-5"`, { parseMode: "plain" });
11786
11839
  }
11787
11840
  break;
11788
11841
  }
@@ -11795,7 +11848,7 @@ async function handleWizardText(chatId, text, channel) {
11795
11848
  pending.step = "backend";
11796
11849
  await promptBackend(chatId, channel);
11797
11850
  } catch {
11798
- await channel.sendText(chatId, `"${tz}" is not a valid timezone. Try again or tap a button.`, "plain");
11851
+ await channel.sendText(chatId, `"${tz}" is not a valid timezone. Try again or tap a button.`, { parseMode: "plain" });
11799
11852
  }
11800
11853
  break;
11801
11854
  }
@@ -11806,7 +11859,7 @@ async function handleWizardText(chatId, text, channel) {
11806
11859
  pending.step = "model";
11807
11860
  await promptModel(chatId, channel);
11808
11861
  } else {
11809
- await channel.sendText(chatId, `Unknown backend. Available: ${getAvailableBackendIds().join(", ")}`, "plain");
11862
+ await channel.sendText(chatId, `Unknown backend. Available: ${getAvailableBackendIds().join(", ")}`, { parseMode: "plain" });
11810
11863
  }
11811
11864
  break;
11812
11865
  }
@@ -11819,7 +11872,7 @@ async function handleWizardText(chatId, text, channel) {
11819
11872
  pending.step = "thinking";
11820
11873
  await promptThinking(chatId, channel);
11821
11874
  } else {
11822
- await channel.sendText(chatId, `Unknown model. Choose: ${Object.keys(adapter.availableModels).join(", ")}`, "plain");
11875
+ await channel.sendText(chatId, `Unknown model. Choose: ${Object.keys(adapter.availableModels).join(", ")}`, { parseMode: "plain" });
11823
11876
  }
11824
11877
  break;
11825
11878
  }
@@ -11837,7 +11890,7 @@ async function handleWizardText(chatId, text, channel) {
11837
11890
  pending.step = "session";
11838
11891
  await promptSession(chatId, channel);
11839
11892
  } else if (isNaN(val) || val < TIMEOUT_MIN_SECONDS || val > TIMEOUT_MAX_SECONDS) {
11840
- await channel.sendText(chatId, `Invalid timeout. Enter a value between ${TIMEOUT_MIN_SECONDS} and ${TIMEOUT_MAX_SECONDS} seconds, or "default" for ${TIMEOUT_DEFAULT_SECONDS}s.`, "plain");
11893
+ await channel.sendText(chatId, `Invalid timeout. Enter a value between ${TIMEOUT_MIN_SECONDS} and ${TIMEOUT_MAX_SECONDS} seconds, or "default" for ${TIMEOUT_DEFAULT_SECONDS}s.`, { parseMode: "plain" });
11841
11894
  } else {
11842
11895
  pending.timeout = val;
11843
11896
  pending.step = "session";
@@ -11852,7 +11905,7 @@ async function handleWizardText(chatId, text, channel) {
11852
11905
  pending.step = "delivery";
11853
11906
  await promptDelivery(chatId, channel);
11854
11907
  } else {
11855
- await channel.sendText(chatId, "Choose: isolated or main", "plain");
11908
+ await channel.sendText(chatId, "Choose: isolated or main", { parseMode: "plain" });
11856
11909
  }
11857
11910
  break;
11858
11911
  }
@@ -11865,13 +11918,13 @@ async function handleWizardText(chatId, text, channel) {
11865
11918
  await promptTarget(chatId, channel);
11866
11919
  } else if (dm === "webhook") {
11867
11920
  pending.step = "target";
11868
- await channel.sendText(chatId, "Enter the webhook URL:", "plain");
11921
+ await channel.sendText(chatId, "Enter the webhook URL:", { parseMode: "plain" });
11869
11922
  } else {
11870
11923
  pending.step = "confirm";
11871
11924
  await promptConfirm(chatId, channel);
11872
11925
  }
11873
11926
  } else {
11874
- await channel.sendText(chatId, "Choose: announce, webhook, or none", "plain");
11927
+ await channel.sendText(chatId, "Choose: announce, webhook, or none", { parseMode: "plain" });
11875
11928
  }
11876
11929
  break;
11877
11930
  }
@@ -11889,13 +11942,13 @@ async function handleWizardText(chatId, text, channel) {
11889
11942
  break;
11890
11943
  }
11891
11944
  default:
11892
- await channel.sendText(chatId, "Please use the buttons to select an option, or type 'cancel' to abort.", "plain");
11945
+ await channel.sendText(chatId, "Please use the buttons to select an option, or type 'cancel' to abort.", { parseMode: "plain" });
11893
11946
  }
11894
11947
  }
11895
11948
  async function handleWizardCallback(chatId, data, channel) {
11896
11949
  const pending = pendingJobs.get(chatId);
11897
11950
  if (!pending) {
11898
- await channel.sendText(chatId, "No active scheduling wizard. Use /schedule to start one.", "plain");
11951
+ await channel.sendText(chatId, "No active scheduling wizard. Use /schedule to start one.", { parseMode: "plain" });
11899
11952
  return;
11900
11953
  }
11901
11954
  resetWizardTimeout(chatId);
@@ -11903,20 +11956,20 @@ async function handleWizardCallback(chatId, data, channel) {
11903
11956
  const tz = data.slice(9);
11904
11957
  pending.timezone = COMMON_TIMEZONES[tz] ?? tz;
11905
11958
  pending.step = "backend";
11906
- await channel.sendText(chatId, `Timezone: ${pending.timezone}`, "plain");
11959
+ await channel.sendText(chatId, `Timezone: ${pending.timezone}`, { parseMode: "plain" });
11907
11960
  await promptBackend(chatId, channel);
11908
11961
  } else if (data.startsWith("sched:backend:")) {
11909
11962
  const backend2 = data.slice(14);
11910
11963
  pending.backend = backend2;
11911
11964
  pending.step = "model";
11912
11965
  const adapter = getAdapter(backend2);
11913
- await channel.sendText(chatId, `Backend: ${adapter.displayName}`, "plain");
11966
+ await channel.sendText(chatId, `Backend: ${adapter.displayName}`, { parseMode: "plain" });
11914
11967
  await promptModel(chatId, channel);
11915
11968
  } else if (data.startsWith("sched:model:")) {
11916
11969
  const model2 = data.slice(12);
11917
11970
  pending.model = model2;
11918
11971
  pending.step = "thinking";
11919
- await channel.sendText(chatId, `Model: ${model2}`, "plain");
11972
+ await channel.sendText(chatId, `Model: ${model2}`, { parseMode: "plain" });
11920
11973
  await promptThinking(chatId, channel);
11921
11974
  } else if (data.startsWith("sched:thinking:")) {
11922
11975
  pending.thinking = data.slice(15);
@@ -11931,7 +11984,7 @@ async function handleWizardCallback(chatId, data, channel) {
11931
11984
  }
11932
11985
  pending.step = "session";
11933
11986
  const label2 = pending.timeout ? `${pending.timeout}s (${Math.round(pending.timeout / 60)} min)` : `Default (${TIMEOUT_DEFAULT_SECONDS}s)`;
11934
- await channel.sendText(chatId, `Timeout: ${label2}`, "plain");
11987
+ await channel.sendText(chatId, `Timeout: ${label2}`, { parseMode: "plain" });
11935
11988
  await promptSession(chatId, channel);
11936
11989
  } else if (data.startsWith("sched:session:")) {
11937
11990
  pending.sessionType = data.slice(14);
@@ -11944,7 +11997,7 @@ async function handleWizardCallback(chatId, data, channel) {
11944
11997
  await promptTarget(chatId, channel);
11945
11998
  } else if (pending.deliveryMode === "webhook") {
11946
11999
  pending.step = "target";
11947
- await channel.sendText(chatId, "Enter the webhook URL:", "plain");
12000
+ await channel.sendText(chatId, "Enter the webhook URL:", { parseMode: "plain" });
11948
12001
  } else {
11949
12002
  pending.step = "confirm";
11950
12003
  await promptConfirm(chatId, channel);
@@ -11953,21 +12006,21 @@ async function handleWizardCallback(chatId, data, channel) {
11953
12006
  pending.target = chatId;
11954
12007
  pending.channel = "telegram";
11955
12008
  pending.step = "confirm";
11956
- await channel.sendText(chatId, "Delivery: this chat", "plain");
12009
+ await channel.sendText(chatId, "Delivery: this chat", { parseMode: "plain" });
11957
12010
  await promptConfirm(chatId, channel);
11958
12011
  } else if (data === "sched:confirm") {
11959
12012
  await finalizeJob(chatId, channel);
11960
12013
  } else if (data === "sched:edit") {
11961
12014
  pending.step = "schedule";
11962
- await channel.sendText(chatId, "Starting over. When should this run?\nSend a schedule or raw cron expression:", "plain");
12015
+ await channel.sendText(chatId, "Starting over. When should this run?\nSend a schedule or raw cron expression:", { parseMode: "plain" });
11963
12016
  } else if (data === "sched:cancel") {
11964
12017
  pendingJobs.delete(chatId);
11965
- await channel.sendText(chatId, "Scheduling cancelled.", "plain");
12018
+ await channel.sendText(chatId, "Scheduling cancelled.", { parseMode: "plain" });
11966
12019
  }
11967
12020
  }
11968
12021
  async function promptTimezone(chatId, channel) {
11969
12022
  if (typeof channel.sendKeyboard !== "function") {
11970
- await channel.sendText(chatId, "Enter a timezone (e.g. America/New_York, UTC, Asia/Jerusalem):", "plain");
12023
+ await channel.sendText(chatId, "Enter a timezone (e.g. America/New_York, UTC, Asia/Jerusalem):", { parseMode: "plain" });
11971
12024
  return;
11972
12025
  }
11973
12026
  const tzButtons = [];
@@ -11983,7 +12036,7 @@ async function promptTimezone(chatId, channel) {
11983
12036
  }
11984
12037
  async function promptBackend(chatId, channel) {
11985
12038
  if (typeof channel.sendKeyboard !== "function") {
11986
- await channel.sendText(chatId, `Enter backend (${getAvailableBackendIds().join(", ")}):`, "plain");
12039
+ await channel.sendText(chatId, `Enter backend (${getAvailableBackendIds().join(", ")}):`, { parseMode: "plain" });
11987
12040
  return;
11988
12041
  }
11989
12042
  const adapters2 = getAvailableAdapters();
@@ -11999,7 +12052,7 @@ async function promptModel(chatId, channel) {
11999
12052
  const adapter = getAdapter(pending.backend);
12000
12053
  if (typeof channel.sendKeyboard !== "function") {
12001
12054
  const models = Object.keys(adapter.availableModels).join(", ");
12002
- await channel.sendText(chatId, `Enter model (${models}):`, "plain");
12055
+ await channel.sendText(chatId, `Enter model (${models}):`, { parseMode: "plain" });
12003
12056
  return;
12004
12057
  }
12005
12058
  const buttons = Object.entries(adapter.availableModels).map(([id, info]) => [{
@@ -12015,7 +12068,7 @@ async function promptThinking(chatId, channel) {
12015
12068
  const modelInfo = adapter.availableModels[pending.model];
12016
12069
  if (modelInfo?.thinking === "adjustable" && modelInfo.thinkingLevels) {
12017
12070
  if (typeof channel.sendKeyboard !== "function") {
12018
- await channel.sendText(chatId, `Enter thinking level (${modelInfo.thinkingLevels.join(", ")}):`, "plain");
12071
+ await channel.sendText(chatId, `Enter thinking level (${modelInfo.thinkingLevels.join(", ")}):`, { parseMode: "plain" });
12019
12072
  return;
12020
12073
  }
12021
12074
  const buttons = modelInfo.thinkingLevels.map((level) => [{
@@ -12031,7 +12084,7 @@ async function promptThinking(chatId, channel) {
12031
12084
  }
12032
12085
  async function promptTimeout(chatId, channel) {
12033
12086
  if (typeof channel.sendKeyboard !== "function") {
12034
- await channel.sendText(chatId, `Job timeout in seconds (${TIMEOUT_MIN_SECONDS}-${TIMEOUT_MAX_SECONDS}), or "default" for ${TIMEOUT_DEFAULT_SECONDS}s:`, "plain");
12087
+ await channel.sendText(chatId, `Job timeout in seconds (${TIMEOUT_MIN_SECONDS}-${TIMEOUT_MAX_SECONDS}), or "default" for ${TIMEOUT_DEFAULT_SECONDS}s:`, { parseMode: "plain" });
12035
12088
  return;
12036
12089
  }
12037
12090
  await channel.sendKeyboard(chatId, "Maximum runtime for this job?", [
@@ -12048,7 +12101,7 @@ async function promptTimeout(chatId, channel) {
12048
12101
  }
12049
12102
  async function promptSession(chatId, channel) {
12050
12103
  if (typeof channel.sendKeyboard !== "function") {
12051
- await channel.sendText(chatId, "Session type: isolated (fresh, no history) or main (uses conversation context)?", "plain");
12104
+ await channel.sendText(chatId, "Session type: isolated (fresh, no history) or main (uses conversation context)?", { parseMode: "plain" });
12052
12105
  return;
12053
12106
  }
12054
12107
  await channel.sendKeyboard(chatId, "Session type for this job?", [
@@ -12058,7 +12111,7 @@ async function promptSession(chatId, channel) {
12058
12111
  }
12059
12112
  async function promptDelivery(chatId, channel) {
12060
12113
  if (typeof channel.sendKeyboard !== "function") {
12061
- await channel.sendText(chatId, "Delivery mode: announce (send to chat), webhook (HTTP POST), or none (silent)?", "plain");
12114
+ await channel.sendText(chatId, "Delivery mode: announce (send to chat), webhook (HTTP POST), or none (silent)?", { parseMode: "plain" });
12062
12115
  return;
12063
12116
  }
12064
12117
  await channel.sendKeyboard(chatId, "How should results be delivered?", [
@@ -12069,13 +12122,13 @@ async function promptDelivery(chatId, channel) {
12069
12122
  }
12070
12123
  async function promptTarget(chatId, channel) {
12071
12124
  if (typeof channel.sendKeyboard !== "function") {
12072
- await channel.sendText(chatId, "Enter the chat ID or group ID for delivery (or send 'this' for this chat):", "plain");
12125
+ await channel.sendText(chatId, "Enter the chat ID or group ID for delivery (or send 'this' for this chat):", { parseMode: "plain" });
12073
12126
  return;
12074
12127
  }
12075
12128
  await channel.sendKeyboard(chatId, "Where should results be sent?", [
12076
12129
  [{ label: "This chat", data: "sched:target:this" }]
12077
12130
  ]);
12078
- await channel.sendText(chatId, "Or send a chat/group ID (e.g. -100123456789 or -100123456789:topic:42):", "plain");
12131
+ await channel.sendText(chatId, "Or send a chat/group ID (e.g. -100123456789 or -100123456789:topic:42):", { parseMode: "plain" });
12079
12132
  }
12080
12133
  async function promptConfirm(chatId, channel) {
12081
12134
  const pending = pendingJobs.get(chatId);
@@ -12106,7 +12159,7 @@ async function promptConfirm(chatId, channel) {
12106
12159
  [{ label: "Edit", data: "sched:edit" }, { label: "Cancel", data: "sched:cancel" }]
12107
12160
  ]);
12108
12161
  } else {
12109
- await channel.sendText(chatId, lines.join("\n") + "\n\nSend 'confirm' to create or 'cancel' to abort.", "plain");
12162
+ await channel.sendText(chatId, lines.join("\n") + "\n\nSend 'confirm' to create or 'cancel' to abort.", { parseMode: "plain" });
12110
12163
  }
12111
12164
  }
12112
12165
  async function finalizeJob(chatId, channel) {
@@ -12144,7 +12197,7 @@ async function finalizeJob(chatId, channel) {
12144
12197
  Task: ${jobParams.description}
12145
12198
  Schedule: ${jobParams.cron ?? "N/A"}
12146
12199
  Timezone: ${jobParams.timezone}`,
12147
- "plain"
12200
+ { parseMode: "plain" }
12148
12201
  );
12149
12202
  } else {
12150
12203
  const job = insertJob({ ...jobParams, chatId });
@@ -12159,17 +12212,17 @@ Task: ${job.description}
12159
12212
  Schedule: ${job.cron ?? "N/A"}
12160
12213
  Timezone: ${job.timezone}
12161
12214
  Next run will be at the scheduled time.`,
12162
- "plain"
12215
+ { parseMode: "plain" }
12163
12216
  );
12164
12217
  }
12165
12218
  } catch (err) {
12166
- await channel.sendText(chatId, `Failed to ${editJobId ? "update" : "create"} job: ${errorMessage(err)}`, "plain");
12219
+ await channel.sendText(chatId, `Failed to ${editJobId ? "update" : "create"} job: ${errorMessage(err)}`, { parseMode: "plain" });
12167
12220
  }
12168
12221
  }
12169
12222
  async function startEditWizard(chatId, jobId, channel) {
12170
12223
  const job = getJobById(jobId);
12171
12224
  if (!job) {
12172
- await channel.sendText(chatId, `Job #${jobId} not found.`, "plain");
12225
+ await channel.sendText(chatId, `Job #${jobId} not found.`, { parseMode: "plain" });
12173
12226
  return;
12174
12227
  }
12175
12228
  const pending = {
@@ -12201,7 +12254,7 @@ Current schedule: ${schedule2}
12201
12254
  Current task: ${job.description}
12202
12255
 
12203
12256
  Send a new schedule, or type 'keep' to keep the current one:`,
12204
- "plain"
12257
+ { parseMode: "plain" }
12205
12258
  );
12206
12259
  }
12207
12260
  var WIZARD_TIMEOUT_MS, wizardTimers, pendingJobs;
@@ -12521,22 +12574,22 @@ async function handleBackendCommand(command, chatId, channel) {
12521
12574
  try {
12522
12575
  adapter = getAdapterForChat(chatId);
12523
12576
  } catch {
12524
- await channel.sendText(chatId, "No backend set. Use /backend first.", "plain");
12577
+ await channel.sendText(chatId, "No backend set. Use /backend first.", { parseMode: "plain" });
12525
12578
  return;
12526
12579
  }
12527
12580
  const sessionId = getSessionId(chatId);
12528
12581
  if (!sessionId) {
12529
- await channel.sendText(chatId, "No active session. Start a conversation first.", "plain");
12582
+ await channel.sendText(chatId, "No active session. Start a conversation first.", { parseMode: "plain" });
12530
12583
  return;
12531
12584
  }
12532
12585
  const cwd = getCwd(chatId) ?? `${process.env.HOME ?? "/tmp"}/.cc-claw/workspace`;
12533
12586
  try {
12534
12587
  const output2 = await spawnBackendCommand(adapter, command, sessionId, cwd);
12535
12588
  const formatted = buildNativeResponse(adapter.id, command, output2);
12536
- await channel.sendText(chatId, formatted, "html");
12589
+ await channel.sendText(chatId, formatted, { parseMode: "html" });
12537
12590
  } catch (err) {
12538
12591
  const msg = err instanceof Error ? err.message : String(err);
12539
- await channel.sendText(chatId, `Backend command failed: ${msg}`, "plain");
12592
+ await channel.sendText(chatId, `Backend command failed: ${msg}`, { parseMode: "plain" });
12540
12593
  }
12541
12594
  }
12542
12595
  function spawnBackendCommand(adapter, command, sessionId, cwd) {
@@ -12620,6 +12673,7 @@ var init_pagination = __esm({
12620
12673
  var propose_exports = {};
12621
12674
  __export(propose_exports, {
12622
12675
  buildEvolveMenuKeyboard: () => buildEvolveMenuKeyboard,
12676
+ buildEvolveOnboardingKeyboard: () => buildEvolveOnboardingKeyboard,
12623
12677
  buildModelKeyboard: () => buildModelKeyboard,
12624
12678
  buildProposalKeyboard: () => buildProposalKeyboard,
12625
12679
  buildUndoKeyboard: () => buildUndoKeyboard,
@@ -12632,16 +12686,20 @@ function pct(ratio) {
12632
12686
  return `${Math.round(ratio * 100)}%`;
12633
12687
  }
12634
12688
  function formatProposalCard(params) {
12635
- const { category, insight, why, targetFile, confidence } = params;
12689
+ const { category, insight, why, targetFile, confidence, proposedAction, proposedDiff } = params;
12636
12690
  const header2 = `[${category}] ${insight}`;
12637
- const whyLine = `Why: ${why}`;
12691
+ const whyLine = why ? `Why: ${why}` : null;
12638
12692
  const confidencePct = pct(confidence);
12639
12693
  const showTarget = category !== "codebase" && targetFile && targetFile !== "codebase";
12640
12694
  const targetLine = showTarget ? `Target: ${targetFile} | Confidence: ${confidencePct}` : `Confidence: ${confidencePct}`;
12641
- return `${header2}
12642
-
12643
- ${whyLine}
12644
- ${targetLine}`;
12695
+ const lines = [header2, ""];
12696
+ if (whyLine) lines.push(whyLine);
12697
+ lines.push(targetLine);
12698
+ if (proposedDiff) {
12699
+ const actionLabel = proposedAction ? ` (${proposedAction})` : "";
12700
+ lines.push("", `Proposed change${actionLabel}:`, "```diff", proposedDiff, "```");
12701
+ }
12702
+ return lines.join("\n");
12645
12703
  }
12646
12704
  function formatNightlySummary(insights) {
12647
12705
  const count = insights.length;
@@ -12686,7 +12744,7 @@ function buildProposalKeyboard(insightId, category) {
12686
12744
  return [
12687
12745
  [
12688
12746
  { label: "Show Diff", data: `evolve:diff:${insightId}`, style: "primary" },
12689
- { label: "Skip", data: `evolve:skip:${insightId}` },
12747
+ { label: "Discuss", data: `evolve:discuss:${insightId}` },
12690
12748
  { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
12691
12749
  ]
12692
12750
  ];
@@ -12694,25 +12752,32 @@ function buildProposalKeyboard(insightId, category) {
12694
12752
  return [
12695
12753
  [
12696
12754
  { label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
12697
- { label: "Skip", data: `evolve:skip:${insightId}` },
12755
+ { label: "Discuss", data: `evolve:discuss:${insightId}` },
12698
12756
  { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
12699
12757
  ]
12700
12758
  ];
12701
12759
  }
12702
- function buildEvolveMenuKeyboard() {
12760
+ function buildEvolveOnboardingKeyboard() {
12761
+ return [
12762
+ [{ label: "\u2705 Enable Self-Learning", data: "evolve:toggle", style: "success" }],
12763
+ [{ label: "One-Time Analysis", data: "evolve:analyze" }]
12764
+ ];
12765
+ }
12766
+ function buildEvolveMenuKeyboard(ctx) {
12767
+ const reviewLabel = ctx.pendingProposals > 0 ? `Review (${ctx.pendingProposals})` : "Review";
12703
12768
  return [
12704
12769
  [
12705
- { label: "Analyze Now", data: "evolve:analyze", style: "success" },
12706
- { label: "Review Proposals", data: "evolve:review" }
12770
+ { label: "\u{1F50D} Analyze Now", data: "evolve:analyze", style: "success" },
12771
+ ctx.pendingProposals > 0 ? { label: reviewLabel, data: "evolve:review", style: "primary" } : { label: reviewLabel, data: "evolve:review" }
12707
12772
  ],
12708
12773
  [
12709
12774
  { label: "Stats", data: "evolve:stats" },
12710
12775
  { label: "History", data: "evolve:history" }
12711
12776
  ],
12712
12777
  [
12713
- { label: "On/Off", data: "evolve:toggle" },
12778
+ { label: "Model", data: "evolve:model" },
12714
12779
  { label: "Undo", data: "evolve:undo", style: "danger" },
12715
- { label: "Model", data: "evolve:model" }
12780
+ { label: "\u23F8 Disable", data: "evolve:toggle", style: "danger" }
12716
12781
  ]
12717
12782
  ];
12718
12783
  }
@@ -12743,6 +12808,7 @@ var init_propose = __esm({
12743
12808
 
12744
12809
  // src/router.ts
12745
12810
  import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir2, readdir as readdir3, stat } from "fs/promises";
12811
+ import { randomUUID as randomUUID3 } from "crypto";
12746
12812
  import { resolve as resolvePath, join as join19 } from "path";
12747
12813
  function parseMcpListOutput(output2) {
12748
12814
  const results = [];
@@ -12941,7 +13007,7 @@ async function sendUnifiedUsage(chatId, channel, view) {
12941
13007
  ];
12942
13008
  await channel.sendKeyboard(chatId, lines.join("\n"), buttons);
12943
13009
  } else {
12944
- await channel.sendText(chatId, lines.join("\n"), "plain");
13010
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
12945
13011
  }
12946
13012
  }
12947
13013
  async function sendUsageLimits(chatId, channel) {
@@ -12975,7 +13041,7 @@ async function sendUsageLimits(chatId, channel) {
12975
13041
  ]);
12976
13042
  await channel.sendKeyboard(chatId, lines.join("\n"), rows);
12977
13043
  } else {
12978
- await channel.sendText(chatId, lines.join("\n"), "plain");
13044
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
12979
13045
  }
12980
13046
  }
12981
13047
  function getMediaRetentionMs() {
@@ -13034,6 +13100,22 @@ function pickFallbackEmoji(userMessage) {
13034
13100
  }
13035
13101
  return "\u{1F44D}";
13036
13102
  }
13103
+ function parseSideQuestPrefix(text) {
13104
+ const match = text.match(/^(?:sq|btw):\s*/i);
13105
+ if (match) return { isSideQuest: true, cleanText: text.slice(match[0].length) };
13106
+ return { isSideQuest: false, cleanText: text };
13107
+ }
13108
+ function getActiveSideQuestCount(chatId) {
13109
+ return activeSideQuests.get(chatId)?.size ?? 0;
13110
+ }
13111
+ function stopAllSideQuests(chatId) {
13112
+ const active = activeSideQuests.get(chatId);
13113
+ if (active) {
13114
+ for (const sqId of active) {
13115
+ stopAgent(sqId);
13116
+ }
13117
+ }
13118
+ }
13037
13119
  async function handleResponseExhaustion(responseText, chatId, msg, channel) {
13038
13120
  const raw = responseText.replace(/\n\n🧠 \[.+$/, "").trim();
13039
13121
  if (raw.length > 300 || !isExhaustedMessage(raw)) return false;
@@ -13122,7 +13204,7 @@ async function handleCommand(msg, channel) {
13122
13204
  ([cat, cmds]) => `${cat}:
13123
13205
  ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
13124
13206
  ).join("\n\n"),
13125
- "plain"
13207
+ { parseMode: "plain" }
13126
13208
  );
13127
13209
  }
13128
13210
  break;
@@ -13141,17 +13223,18 @@ ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
13141
13223
  await channel.sendText(
13142
13224
  chatId,
13143
13225
  "CC-Claw Menu:\n/newchat \xB7 /status \xB7 /backend \xB7 /model\n/jobs \xB7 /memory \xB7 /history \xB7 /help",
13144
- "plain"
13226
+ { parseMode: "plain" }
13145
13227
  );
13146
13228
  }
13147
13229
  break;
13148
13230
  }
13149
13231
  case "stop": {
13150
13232
  const stopped = stopAgent(chatId);
13233
+ stopAllSideQuests(chatId);
13151
13234
  await channel.sendText(
13152
13235
  chatId,
13153
13236
  stopped ? "Stopping current task..." : "Nothing is running.",
13154
- "plain"
13237
+ { parseMode: "plain" }
13155
13238
  );
13156
13239
  if (stopped && typeof channel.sendKeyboard === "function") {
13157
13240
  await channel.sendKeyboard(chatId, "", [
@@ -13174,7 +13257,7 @@ ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
13174
13257
  for (const [id, label2] of Object.entries(PERM_MODES)) {
13175
13258
  lines.push(`${id === currentMode ? "\u2713 " : " "}/permissions ${id} \u2014 ${label2}`);
13176
13259
  }
13177
- await channel.sendText(chatId, lines.join("\n"), "plain");
13260
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
13178
13261
  }
13179
13262
  break;
13180
13263
  }
@@ -13191,7 +13274,7 @@ ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
13191
13274
  const lines = Object.entries(VERBOSE_LEVELS).map(
13192
13275
  ([id, label2]) => `${id === currentVerbose ? "\u2713 " : " "}${id} \u2014 ${label2}`
13193
13276
  );
13194
- await channel.sendText(chatId, ["Tool visibility:", "", ...lines].join("\n"), "plain");
13277
+ await channel.sendText(chatId, ["Tool visibility:", "", ...lines].join("\n"), { parseMode: "plain" });
13195
13278
  }
13196
13279
  break;
13197
13280
  }
@@ -13218,12 +13301,13 @@ Tap to toggle:`,
13218
13301
  );
13219
13302
  } else {
13220
13303
  const lines = ALL_TOOLS.map((t) => `${toolsMap[t] ? "[on] " : "[off]"} ${t}`);
13221
- await channel.sendText(chatId, ["Allowed tools:", "", ...lines].join("\n"), "plain");
13304
+ await channel.sendText(chatId, ["Allowed tools:", "", ...lines].join("\n"), { parseMode: "plain" });
13222
13305
  }
13223
13306
  break;
13224
13307
  }
13225
13308
  case "new":
13226
13309
  case "newchat": {
13310
+ stopAllSideQuests(chatId);
13227
13311
  const oldSessionId = getSessionId(chatId);
13228
13312
  const exchangeCount = getMessagePairCount(chatId);
13229
13313
  const summarized = await summarizeSession(chatId);
@@ -13247,7 +13331,7 @@ Tap to toggle:`,
13247
13331
  }
13248
13332
  } else {
13249
13333
  const msg2 = summarized ? "Session summarized and saved. Fresh conversation started!" : "Fresh conversation started. What's on your mind?";
13250
- await channel.sendText(chatId, msg2, "plain");
13334
+ await channel.sendText(chatId, msg2, { parseMode: "plain" });
13251
13335
  }
13252
13336
  break;
13253
13337
  }
@@ -13255,12 +13339,12 @@ Tap to toggle:`,
13255
13339
  if (commandArgs?.toLowerCase() === "all") {
13256
13340
  const pendingIds = getLoggedChatIds();
13257
13341
  if (pendingIds.length === 0) {
13258
- await channel.sendText(chatId, "No pending sessions to summarize.", "plain");
13342
+ await channel.sendText(chatId, "No pending sessions to summarize.", { parseMode: "plain" });
13259
13343
  break;
13260
13344
  }
13261
- await channel.sendText(chatId, `Summarizing ${pendingIds.length} pending session(s)...`, "plain");
13345
+ await channel.sendText(chatId, `Summarizing ${pendingIds.length} pending session(s)...`, { parseMode: "plain" });
13262
13346
  await summarizeAllPending();
13263
- await channel.sendText(chatId, `Done. ${pendingIds.length} session(s) summarized and saved to memory.`, "plain");
13347
+ await channel.sendText(chatId, `Done. ${pendingIds.length} session(s) summarized and saved to memory.`, { parseMode: "plain" });
13264
13348
  } else {
13265
13349
  const pairs = getMessagePairCount(chatId);
13266
13350
  if (pairs < 2) {
@@ -13268,16 +13352,16 @@ Tap to toggle:`,
13268
13352
  await channel.sendText(
13269
13353
  chatId,
13270
13354
  "Session log was cleared (auto-compact or service restart). Your conversation history is preserved in episodic memory. Continue chatting normally, or use /newchat to start fresh.",
13271
- "plain"
13355
+ { parseMode: "plain" }
13272
13356
  );
13273
13357
  } else {
13274
- await channel.sendText(chatId, "Not enough conversation to summarize (need at least 2 exchanges).", "plain");
13358
+ await channel.sendText(chatId, "Not enough conversation to summarize (need at least 2 exchanges).", { parseMode: "plain" });
13275
13359
  }
13276
13360
  break;
13277
13361
  }
13278
13362
  const progressMsgId = await channel.sendTextReturningId?.(chatId, `Summarizing ${pairs} exchanges...`, "plain");
13279
13363
  if (!progressMsgId) {
13280
- await channel.sendText(chatId, `Summarizing ${pairs} exchanges...`, "plain");
13364
+ await channel.sendText(chatId, `Summarizing ${pairs} exchanges...`, { parseMode: "plain" });
13281
13365
  }
13282
13366
  const success2 = await summarizeSession(chatId);
13283
13367
  if (success2) {
@@ -13311,14 +13395,14 @@ Tap to toggle:`,
13311
13395
  }
13312
13396
  }
13313
13397
  } else {
13314
- await channel.sendText(chatId, doneText, "plain");
13398
+ await channel.sendText(chatId, doneText, { parseMode: "plain" });
13315
13399
  }
13316
13400
  } else {
13317
13401
  const failText = "Summarization failed. Session log preserved for retry.";
13318
13402
  if (progressMsgId) {
13319
13403
  await channel.editText?.(chatId, progressMsgId, failText, "plain");
13320
13404
  } else {
13321
- await channel.sendText(chatId, failText, "plain");
13405
+ await channel.sendText(chatId, failText, { parseMode: "plain" });
13322
13406
  }
13323
13407
  }
13324
13408
  }
@@ -13383,6 +13467,7 @@ Tap to toggle:`,
13383
13467
  const iQuick = iStats.chat;
13384
13468
  const iDeep = iStats.agentic;
13385
13469
  const intentLine = iTotal > 0 ? `\u26A1 ${iTotal} messages: ${iQuick} quick, ${iDeep} deep` : `\u26A1 No messages classified yet`;
13470
+ const sqCount = getActiveSideQuestCount(chatId);
13386
13471
  const lines = [
13387
13472
  `\u{1F43E} CC-Claw v${VERSION}`,
13388
13473
  `\u23F1 Uptime: ${uptimeStr}`,
@@ -13397,6 +13482,7 @@ Tap to toggle:`,
13397
13482
  `\u{1F4CB} ${sessionId ? sessionId.slice(0, 12) + "..." : "no active session"}`,
13398
13483
  `\u{1F4C1} ${cwd ?? "default workspace"}`,
13399
13484
  `\u{1F4D0} Context: ${ctxBar} ${usedK}K/${maxK}K (${contextPct.toFixed(1)}%)`,
13485
+ ...sqCount > 0 ? [`\u{1F5FA} Side quests: ${sqCount} active`] : [],
13400
13486
  ``,
13401
13487
  buildSectionHeader("Usage"),
13402
13488
  `\u{1F4E8} ${usage2.request_count} requests \xB7 ${(usage2.input_tokens / 1e3).toFixed(0)}K in \xB7 ${(usage2.output_tokens / 1e3).toFixed(0)}K out`,
@@ -13418,7 +13504,7 @@ Tap to toggle:`,
13418
13504
  ]
13419
13505
  ]);
13420
13506
  } else {
13421
- await channel.sendText(chatId, lines.join("\n"), "plain");
13507
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
13422
13508
  }
13423
13509
  break;
13424
13510
  }
@@ -13441,7 +13527,7 @@ Tap to toggle:`,
13441
13527
  const lines = adapters2.map(
13442
13528
  (a) => `${a.id === currentBackend ? "\u2713 " : " "}${a.displayName} (/backend ${a.id})`
13443
13529
  );
13444
- await channel.sendText(chatId, ["Available backends:", "", ...lines].join("\n"), "plain");
13530
+ await channel.sendText(chatId, ["Available backends:", "", ...lines].join("\n"), { parseMode: "plain" });
13445
13531
  }
13446
13532
  break;
13447
13533
  }
@@ -13452,14 +13538,14 @@ Tap to toggle:`,
13452
13538
  if (getAllBackendIds().includes(backendId)) {
13453
13539
  await sendBackendSwitchConfirmation(chatId, backendId, channel);
13454
13540
  } else {
13455
- await channel.sendText(chatId, `Backend "${command}" is not available.`, "plain");
13541
+ await channel.sendText(chatId, `Backend "${command}" is not available.`, { parseMode: "plain" });
13456
13542
  }
13457
13543
  break;
13458
13544
  }
13459
13545
  case "gemini_accounts": {
13460
13546
  const slots = getGeminiSlots();
13461
13547
  if (slots.length === 0) {
13462
- await channel.sendText(chatId, "No Gemini credentials configured.\nAdd with: <code>cc-claw gemini add-key</code> or <code>cc-claw gemini add-account</code>", "html");
13548
+ await channel.sendText(chatId, "No Gemini credentials configured.\nAdd with: <code>cc-claw gemini add-key</code> or <code>cc-claw gemini add-account</code>", { parseMode: "html" });
13463
13549
  break;
13464
13550
  }
13465
13551
  if (typeof channel.sendKeyboard === "function") {
@@ -13493,7 +13579,7 @@ Tap to toggle:`,
13493
13579
  ${list}
13494
13580
 
13495
13581
  Rotation mode: ${currentMode}
13496
- Use: /gemini_accounts <name> to pin`, "plain");
13582
+ Use: /gemini_accounts <name> to pin`, { parseMode: "plain" });
13497
13583
  }
13498
13584
  break;
13499
13585
  }
@@ -13502,7 +13588,7 @@ Use: /gemini_accounts <name> to pin`, "plain");
13502
13588
  if (getAllBackendIds().includes(backendId)) {
13503
13589
  await sendBackendSwitchConfirmation(chatId, backendId, channel);
13504
13590
  } else {
13505
- await channel.sendText(chatId, `Backend "${command}" is not available.`, "plain");
13591
+ await channel.sendText(chatId, `Backend "${command}" is not available.`, { parseMode: "plain" });
13506
13592
  }
13507
13593
  break;
13508
13594
  }
@@ -13511,7 +13597,7 @@ Use: /gemini_accounts <name> to pin`, "plain");
13511
13597
  try {
13512
13598
  adapter = getAdapterForChat(chatId);
13513
13599
  } catch {
13514
- await channel.sendText(chatId, "No backend set. Use /backend first.", "plain");
13600
+ await channel.sendText(chatId, "No backend set. Use /backend first.", { parseMode: "plain" });
13515
13601
  break;
13516
13602
  }
13517
13603
  const models = adapter.availableModels;
@@ -13530,7 +13616,7 @@ Use: /gemini_accounts <name> to pin`, "plain");
13530
13616
  const lines = Object.entries(models).map(
13531
13617
  ([id, info]) => `${id === current ? "\u2713 " : " "}${id} \u2014 ${info.label}`
13532
13618
  );
13533
- await channel.sendText(chatId, [`Models (${adapter.displayName}):`, "", ...lines].join("\n"), "plain");
13619
+ await channel.sendText(chatId, [`Models (${adapter.displayName}):`, "", ...lines].join("\n"), { parseMode: "plain" });
13534
13620
  }
13535
13621
  break;
13536
13622
  }
@@ -13562,7 +13648,7 @@ Use: /gemini_accounts <name> to pin`, "plain");
13562
13648
  } else {
13563
13649
  await channel.sendText(chatId, `Summarizer: ${currentLabel}
13564
13650
 
13565
- Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, "plain");
13651
+ Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, { parseMode: "plain" });
13566
13652
  }
13567
13653
  break;
13568
13654
  }
@@ -13574,24 +13660,24 @@ Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, "plain
13574
13660
  if (parts.length >= 3) {
13575
13661
  const [bk, win, val] = parts;
13576
13662
  if (!getAllBackendIds().includes(bk)) {
13577
- await channel.sendText(chatId, `Unknown backend: ${bk}. Available: ${getAllBackendIds().join(", ")}`, "plain");
13663
+ await channel.sendText(chatId, `Unknown backend: ${bk}. Available: ${getAllBackendIds().join(", ")}`, { parseMode: "plain" });
13578
13664
  break;
13579
13665
  }
13580
13666
  if (!["hourly", "daily", "weekly"].includes(win)) {
13581
- await channel.sendText(chatId, "Window must be: hourly, daily, or weekly", "plain");
13667
+ await channel.sendText(chatId, "Window must be: hourly, daily, or weekly", { parseMode: "plain" });
13582
13668
  break;
13583
13669
  }
13584
13670
  if (val === "off") {
13585
13671
  clearBackendLimit(bk, win);
13586
- await channel.sendText(chatId, `Limit removed for ${bk} (${win}).`, "plain");
13672
+ await channel.sendText(chatId, `Limit removed for ${bk} (${win}).`, { parseMode: "plain" });
13587
13673
  } else {
13588
13674
  const tokens = parseInt(val, 10);
13589
13675
  if (isNaN(tokens) || tokens <= 0) {
13590
- await channel.sendText(chatId, "Token limit must be a positive number.", "plain");
13676
+ await channel.sendText(chatId, "Token limit must be a positive number.", { parseMode: "plain" });
13591
13677
  break;
13592
13678
  }
13593
13679
  setBackendLimit(bk, win, tokens);
13594
- await channel.sendText(chatId, `Limit set: ${bk} (${win}) = ${(tokens / 1e3).toFixed(0)}K input tokens`, "plain");
13680
+ await channel.sendText(chatId, `Limit set: ${bk} (${win}) = ${(tokens / 1e3).toFixed(0)}K input tokens`, { parseMode: "plain" });
13595
13681
  }
13596
13682
  break;
13597
13683
  }
@@ -13609,7 +13695,7 @@ Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, "plain
13609
13695
  current ? `Working directory: ${current}
13610
13696
 
13611
13697
  No saved bookmarks yet. Set a directory with /cwd <path> to auto-save.` : "No working directory set. Usage: /cwd ~/projects/my-app",
13612
- "plain"
13698
+ { parseMode: "plain" }
13613
13699
  );
13614
13700
  return;
13615
13701
  }
@@ -13622,46 +13708,46 @@ Recent directories:` : "Recent directories:";
13622
13708
  } else {
13623
13709
  const list = recents.map((r) => ` ${r.alias} \u2192 ${r.path}`).join("\n");
13624
13710
  await channel.sendText(chatId, `${text}
13625
- ${list}`, "plain");
13711
+ ${list}`, { parseMode: "plain" });
13626
13712
  }
13627
13713
  return;
13628
13714
  }
13629
13715
  if (commandArgs === "reset" || commandArgs === "clear") {
13630
13716
  clearCwd(chatId);
13631
- await channel.sendText(chatId, "Working directory cleared. Using default.", "plain");
13717
+ await channel.sendText(chatId, "Working directory cleared. Using default.", { parseMode: "plain" });
13632
13718
  return;
13633
13719
  }
13634
13720
  if (commandArgs === "aliases") {
13635
13721
  const all = getAllBookmarks(chatId);
13636
13722
  if (all.length === 0) {
13637
- await channel.sendText(chatId, "No bookmarks saved yet.", "plain");
13723
+ await channel.sendText(chatId, "No bookmarks saved yet.", { parseMode: "plain" });
13638
13724
  return;
13639
13725
  }
13640
13726
  const lines = all.map((b) => ` ${b.manual ? "[manual]" : "[auto]"} ${b.alias} \u2192 ${b.path}`);
13641
13727
  await channel.sendText(chatId, `Directory bookmarks:
13642
- ${lines.join("\n")}`, "plain");
13728
+ ${lines.join("\n")}`, { parseMode: "plain" });
13643
13729
  return;
13644
13730
  }
13645
13731
  if (commandArgs.startsWith("unalias ")) {
13646
13732
  const aliasName = commandArgs.slice(8).trim();
13647
13733
  if (!aliasName) {
13648
- await channel.sendText(chatId, "Usage: /cwd unalias <name>", "plain");
13734
+ await channel.sendText(chatId, "Usage: /cwd unalias <name>", { parseMode: "plain" });
13649
13735
  return;
13650
13736
  }
13651
13737
  const deleted = deleteBookmark(chatId, aliasName);
13652
- await channel.sendText(chatId, deleted ? `Bookmark '${aliasName}' removed.` : `Bookmark '${aliasName}' not found.`, "plain");
13738
+ await channel.sendText(chatId, deleted ? `Bookmark '${aliasName}' removed.` : `Bookmark '${aliasName}' not found.`, { parseMode: "plain" });
13653
13739
  return;
13654
13740
  }
13655
13741
  if (commandArgs.startsWith("alias ")) {
13656
13742
  const parts = commandArgs.slice(6).trim().split(/\s+/);
13657
13743
  if (parts.length < 2) {
13658
- await channel.sendText(chatId, "Usage: /cwd alias <name> <path>", "plain");
13744
+ await channel.sendText(chatId, "Usage: /cwd alias <name> <path>", { parseMode: "plain" });
13659
13745
  return;
13660
13746
  }
13661
13747
  const [aliasName, ...pathParts] = parts;
13662
13748
  const aliasPath = pathParts.join(" ").replace(/^~/, process.env.HOME ?? "");
13663
13749
  upsertBookmark(chatId, aliasName, aliasPath, true);
13664
- await channel.sendText(chatId, `Bookmark saved: ${aliasName} \u2192 ${aliasPath}`, "plain");
13750
+ await channel.sendText(chatId, `Bookmark saved: ${aliasName} \u2192 ${aliasPath}`, { parseMode: "plain" });
13665
13751
  return;
13666
13752
  }
13667
13753
  const arg = commandArgs;
@@ -13695,31 +13781,31 @@ ${lines.join("\n")}`, "plain");
13695
13781
  await channel.sendKeyboard(chatId, `Multiple matches for "${arg}":`, buttons);
13696
13782
  return;
13697
13783
  }
13698
- await channel.sendText(chatId, `Directory alias '${arg}' not found. Use /cwd aliases to see saved bookmarks.`, "plain");
13784
+ await channel.sendText(chatId, `Directory alias '${arg}' not found. Use /cwd aliases to see saved bookmarks.`, { parseMode: "plain" });
13699
13785
  break;
13700
13786
  }
13701
13787
  case "memory": {
13702
13788
  if (commandArgs?.startsWith("edit ")) {
13703
13789
  const editMatch = commandArgs.match(/^edit\s+(\d+)\s+(.+)/);
13704
13790
  if (!editMatch) {
13705
- await channel.sendText(chatId, "Usage: /memory edit <id> <new content>", "plain");
13791
+ await channel.sendText(chatId, "Usage: /memory edit <id> <new content>", { parseMode: "plain" });
13706
13792
  break;
13707
13793
  }
13708
13794
  const editId = parseInt(editMatch[1], 10);
13709
13795
  const newContent = editMatch[2];
13710
13796
  const mem = getMemoryById(editId);
13711
13797
  if (!mem) {
13712
- await channel.sendText(chatId, "Memory not found.", "plain");
13798
+ await channel.sendText(chatId, "Memory not found.", { parseMode: "plain" });
13713
13799
  break;
13714
13800
  }
13715
13801
  deleteMemoryById(editId);
13716
13802
  saveMemoryWithEmbedding(mem.trigger, newContent, mem.type);
13717
- await channel.sendText(chatId, `Memory #${editId} updated.`, "plain");
13803
+ await channel.sendText(chatId, `Memory #${editId} updated.`, { parseMode: "plain" });
13718
13804
  break;
13719
13805
  }
13720
13806
  const memories = listMemories();
13721
13807
  if (memories.length === 0) {
13722
- await channel.sendText(chatId, "No memories stored yet.", "plain");
13808
+ await channel.sendText(chatId, "No memories stored yet.", { parseMode: "plain" });
13723
13809
  return;
13724
13810
  }
13725
13811
  if (typeof channel.sendKeyboard === "function") {
@@ -13728,19 +13814,19 @@ ${lines.join("\n")}`, "plain");
13728
13814
  const lines = memories.map(
13729
13815
  (m) => `- ${m.trigger}: ${m.content} (salience: ${m.salience.toFixed(2)})`
13730
13816
  );
13731
- await channel.sendText(chatId, lines.join("\n"), "plain");
13817
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
13732
13818
  }
13733
13819
  break;
13734
13820
  }
13735
13821
  case "remember": {
13736
13822
  if (!commandArgs) {
13737
- await channel.sendText(chatId, "Usage: /remember <text>", "plain");
13823
+ await channel.sendText(chatId, "Usage: /remember <text>", { parseMode: "plain" });
13738
13824
  return;
13739
13825
  }
13740
13826
  const content = commandArgs.replace(/^that\s+/i, "");
13741
13827
  const trigger = content.split(/\s+/).slice(0, 3).join(" ");
13742
13828
  saveMemoryWithEmbedding(trigger, content, "semantic");
13743
- await channel.sendText(chatId, "Got it, I'll remember that.", "plain");
13829
+ await channel.sendText(chatId, "Got it, I'll remember that.", { parseMode: "plain" });
13744
13830
  if (typeof channel.sendKeyboard === "function") {
13745
13831
  await channel.sendKeyboard(chatId, "", [
13746
13832
  [{ label: "\u{1F4DA} View Memories", data: "menu:memory" }]
@@ -13750,14 +13836,14 @@ ${lines.join("\n")}`, "plain");
13750
13836
  }
13751
13837
  case "forget": {
13752
13838
  if (!commandArgs) {
13753
- await channel.sendText(chatId, "Usage: /forget <keyword>", "plain");
13839
+ await channel.sendText(chatId, "Usage: /forget <keyword>", { parseMode: "plain" });
13754
13840
  return;
13755
13841
  }
13756
13842
  const count = forgetMemory(commandArgs);
13757
13843
  await channel.sendText(
13758
13844
  chatId,
13759
13845
  count > 0 ? `Forgot ${count} memory(ies) matching "${commandArgs}".` : `No memories found matching "${commandArgs}".`,
13760
- "plain"
13846
+ { parseMode: "plain" }
13761
13847
  );
13762
13848
  break;
13763
13849
  }
@@ -13772,7 +13858,7 @@ ${lines.join("\n")}`, "plain");
13772
13858
  ]);
13773
13859
  } else {
13774
13860
  const toggled = toggleVoice(chatId);
13775
- await channel.sendText(chatId, toggled ? "Voice responses enabled." : "Voice responses disabled.", "plain");
13861
+ await channel.sendText(chatId, toggled ? "Voice responses enabled." : "Voice responses disabled.", { parseMode: "plain" });
13776
13862
  }
13777
13863
  break;
13778
13864
  }
@@ -13791,7 +13877,7 @@ ${lines.join("\n")}`, "plain");
13791
13877
  ]
13792
13878
  ]);
13793
13879
  } else {
13794
- await channel.sendText(chatId, `Current Response Style: ${currentStyle}`, "plain");
13880
+ await channel.sendText(chatId, `Current Response Style: ${currentStyle}`, { parseMode: "plain" });
13795
13881
  }
13796
13882
  break;
13797
13883
  }
@@ -13808,7 +13894,7 @@ Appends model + thinking level to each response.`, [
13808
13894
  } else {
13809
13895
  const newSig = currentSig === "on" ? "off" : "on";
13810
13896
  setModelSignature(chatId, newSig);
13811
- await channel.sendText(chatId, newSig === "on" ? "Model signature enabled." : "Model signature disabled.", "plain");
13897
+ await channel.sendText(chatId, newSig === "on" ? "Model signature enabled." : "Model signature disabled.", { parseMode: "plain" });
13812
13898
  }
13813
13899
  break;
13814
13900
  }
@@ -13818,7 +13904,7 @@ Appends model + thinking level to each response.`, [
13818
13904
  try {
13819
13905
  adapter = getAdapterForChat(chatId);
13820
13906
  } catch {
13821
- await channel.sendText(chatId, "No backend set. Use /backend first.", "plain");
13907
+ await channel.sendText(chatId, "No backend set. Use /backend first.", { parseMode: "plain" });
13822
13908
  break;
13823
13909
  }
13824
13910
  const currentModel = getModel(chatId) ?? adapter.defaultModel;
@@ -13826,7 +13912,7 @@ Appends model + thinking level to each response.`, [
13826
13912
  const currentLevel = getThinkingLevel(chatId) || "auto";
13827
13913
  if (!modelInfo || modelInfo.thinking !== "adjustable" || !modelInfo.thinkingLevels) {
13828
13914
  await channel.sendText(chatId, `Current model (${shortModelName(currentModel)}) doesn't support adjustable thinking.
13829
- Use /model to pick a model with \u26A1 thinking support.`, "plain");
13915
+ Use /model to pick a model with \u26A1 thinking support.`, { parseMode: "plain" });
13830
13916
  break;
13831
13917
  }
13832
13918
  if (typeof channel.sendKeyboard === "function") {
@@ -13844,31 +13930,31 @@ Current: ${capitalize(currentLevel)}`,
13844
13930
  } else {
13845
13931
  await channel.sendText(chatId, `Thinking: ${capitalize(currentLevel)}
13846
13932
  Levels: ${modelInfo.thinkingLevels.join(", ")}
13847
- Set via callback (keyboard required).`, "plain");
13933
+ Set via callback (keyboard required).`, { parseMode: "plain" });
13848
13934
  }
13849
13935
  break;
13850
13936
  }
13851
13937
  case "imagine":
13852
13938
  case "image": {
13853
13939
  if (!commandArgs) {
13854
- await channel.sendText(chatId, "Usage: /imagine <prompt>\nExample: /imagine a cat astronaut on Mars", "plain");
13940
+ await channel.sendText(chatId, "Usage: /imagine <prompt>\nExample: /imagine a cat astronaut on Mars", { parseMode: "plain" });
13855
13941
  return;
13856
13942
  }
13857
13943
  if (!isImageGenAvailable()) {
13858
- await channel.sendText(chatId, "Image generation requires GEMINI_API_KEY. Configure it in ~/.cc-claw/.env", "plain");
13944
+ await channel.sendText(chatId, "Image generation requires GEMINI_API_KEY. Configure it in ~/.cc-claw/.env", { parseMode: "plain" });
13859
13945
  return;
13860
13946
  }
13861
- await channel.sendText(chatId, "\u{1F3A8} Generating image\u2026", "plain");
13947
+ await channel.sendText(chatId, "\u{1F3A8} Generating image\u2026", { parseMode: "plain" });
13862
13948
  try {
13863
13949
  const result = await generateImage(commandArgs);
13864
13950
  const file = await readFile5(result.filePath);
13865
13951
  const name = result.filePath.split("/").pop() ?? "image.png";
13866
13952
  await channel.sendFile(chatId, file, name);
13867
13953
  if (result.text) {
13868
- await channel.sendText(chatId, result.text, "plain");
13954
+ await channel.sendText(chatId, result.text, { parseMode: "plain" });
13869
13955
  }
13870
13956
  } catch (err) {
13871
- await channel.sendText(chatId, `Image generation failed: ${errorMessage(err)}`, "plain");
13957
+ await channel.sendText(chatId, `Image generation failed: ${errorMessage(err)}`, { parseMode: "plain" });
13872
13958
  }
13873
13959
  break;
13874
13960
  }
@@ -13877,7 +13963,7 @@ Set via callback (keyboard required).`, "plain");
13877
13963
  await channel.sendText(
13878
13964
  chatId,
13879
13965
  "Usage: /schedule <description>\nExample: /schedule every day at 9am summarize AI news\n\nThe wizard will guide you through all configuration options.",
13880
- "plain"
13966
+ { parseMode: "plain" }
13881
13967
  );
13882
13968
  return;
13883
13969
  }
@@ -13898,7 +13984,7 @@ Set via callback (keyboard required).`, "plain");
13898
13984
  await channel.sendText(
13899
13985
  chatId,
13900
13986
  success2 ? `Job #${id} cancelled.` : `Job #${id} not found.`,
13901
- "plain"
13987
+ { parseMode: "plain" }
13902
13988
  );
13903
13989
  break;
13904
13990
  }
@@ -13912,7 +13998,7 @@ Set via callback (keyboard required).`, "plain");
13912
13998
  await channel.sendText(
13913
13999
  chatId,
13914
14000
  paused ? `Job #${pauseId} paused. Use /resume ${pauseId} to re-enable.` : `Job #${pauseId} not found.`,
13915
- "plain"
14001
+ { parseMode: "plain" }
13916
14002
  );
13917
14003
  break;
13918
14004
  }
@@ -13926,7 +14012,7 @@ Set via callback (keyboard required).`, "plain");
13926
14012
  await channel.sendText(
13927
14013
  chatId,
13928
14014
  resumed ? `Job #${resumeId} resumed.` : `Job #${resumeId} not found.`,
13929
- "plain"
14015
+ { parseMode: "plain" }
13930
14016
  );
13931
14017
  break;
13932
14018
  }
@@ -13936,9 +14022,9 @@ Set via callback (keyboard required).`, "plain");
13936
14022
  return;
13937
14023
  }
13938
14024
  const runJobId = parseInt(commandArgs, 10);
13939
- await channel.sendText(chatId, `Triggering job #${runJobId}...`, "plain");
14025
+ await channel.sendText(chatId, `Triggering job #${runJobId}...`, { parseMode: "plain" });
13940
14026
  const result = await triggerJob(runJobId);
13941
- await channel.sendText(chatId, result, "plain");
14027
+ await channel.sendText(chatId, result, { parseMode: "plain" });
13942
14028
  break;
13943
14029
  }
13944
14030
  case "runs": {
@@ -13948,7 +14034,7 @@ Set via callback (keyboard required).`, "plain");
13948
14034
  } else {
13949
14035
  const runs = getJobRuns(void 0, 10);
13950
14036
  if (runs.length === 0) {
13951
- await channel.sendText(chatId, "No run history yet.", "plain");
14037
+ await channel.sendText(chatId, "No run history yet.", { parseMode: "plain" });
13952
14038
  return;
13953
14039
  }
13954
14040
  const lines = runs.map((r) => {
@@ -13959,13 +14045,13 @@ Set via callback (keyboard required).`, "plain");
13959
14045
  Tokens: ${r.usageInput}in / ${r.usageOutput}out` : "";
13960
14046
  return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${duration}${error3}${usage2}`;
13961
14047
  });
13962
- await channel.sendText(chatId, lines.join("\n\n"), "plain");
14048
+ await channel.sendText(chatId, lines.join("\n\n"), { parseMode: "plain" });
13963
14049
  }
13964
14050
  break;
13965
14051
  }
13966
14052
  case "editjob": {
13967
14053
  if (!commandArgs) {
13968
- await channel.sendText(chatId, "Usage: /editjob <job-id>", "plain");
14054
+ await channel.sendText(chatId, "Usage: /editjob <job-id>", { parseMode: "plain" });
13969
14055
  return;
13970
14056
  }
13971
14057
  const editId = parseInt(commandArgs, 10);
@@ -13974,7 +14060,7 @@ Set via callback (keyboard required).`, "plain");
13974
14060
  }
13975
14061
  case "health": {
13976
14062
  const report = getHealthReport();
13977
- await channel.sendText(chatId, formatHealthReport(report), "plain");
14063
+ await channel.sendText(chatId, formatHealthReport(report), { parseMode: "plain" });
13978
14064
  if (typeof channel.sendKeyboard === "function") {
13979
14065
  await channel.sendKeyboard(chatId, "", [
13980
14066
  [
@@ -13990,7 +14076,7 @@ Set via callback (keyboard required).`, "plain");
13990
14076
  if (query) {
13991
14077
  const results = searchMessageLog(chatId, query, 20);
13992
14078
  if (results.length === 0) {
13993
- await channel.sendText(chatId, `No matching history found. Try /memories ${query} for older sessions.`, "plain");
14079
+ await channel.sendText(chatId, `No matching history found. Try /memories ${query} for older sessions.`, { parseMode: "plain" });
13994
14080
  break;
13995
14081
  }
13996
14082
  const lines = [`\u{1F4CB} History: "${query}"`, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"];
@@ -14001,14 +14087,14 @@ Set via callback (keyboard required).`, "plain");
14001
14087
  lines.push(`[${row.backend ?? "?"} \xB7 ${date}] ${roleLabel}: ${preview}`);
14002
14088
  }
14003
14089
  lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
14004
- await channel.sendText(chatId, lines.join("\n"), "plain");
14090
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
14005
14091
  } else {
14006
14092
  if (typeof channel.sendKeyboard === "function") {
14007
14093
  await sendHistoryView(chatId, channel, {});
14008
14094
  } else {
14009
14095
  const rows = getRecentMessageLog(chatId, 20).reverse();
14010
14096
  if (rows.length === 0) {
14011
- await channel.sendText(chatId, "No conversation history found.", "plain");
14097
+ await channel.sendText(chatId, "No conversation history found.", { parseMode: "plain" });
14012
14098
  break;
14013
14099
  }
14014
14100
  const lines = ["\u{1F4CB} Recent conversation (last 10 exchanges):", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"];
@@ -14019,7 +14105,7 @@ Set via callback (keyboard required).`, "plain");
14019
14105
  lines.push(`[${row.backend ?? "?"} \xB7 ${date}] ${roleLabel}: ${preview}`);
14020
14106
  }
14021
14107
  lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
14022
- await channel.sendText(chatId, lines.join("\n"), "plain");
14108
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
14023
14109
  }
14024
14110
  }
14025
14111
  break;
@@ -14027,7 +14113,7 @@ Set via callback (keyboard required).`, "plain");
14027
14113
  case "skills": {
14028
14114
  const skills2 = await discoverAllSkills();
14029
14115
  if (skills2.length === 0) {
14030
- await channel.sendText(chatId, "No skills found. Install skills with /skill-install <github-url> or place them in ~/.cc-claw/workspace/skills/", "plain");
14116
+ await channel.sendText(chatId, "No skills found. Install skills with /skill-install <github-url> or place them in ~/.cc-claw/workspace/skills/", { parseMode: "plain" });
14031
14117
  return;
14032
14118
  }
14033
14119
  const page = commandArgs ? parseInt(commandArgs, 10) || 1 : 1;
@@ -14036,16 +14122,16 @@ Set via callback (keyboard required).`, "plain");
14036
14122
  }
14037
14123
  case "skill-install": {
14038
14124
  if (!commandArgs) {
14039
- await channel.sendText(chatId, "Usage: /skill-install <github-url>\nExample: /skill-install jacob-bd/universal-skills-manager", "plain");
14125
+ await channel.sendText(chatId, "Usage: /skill-install <github-url>\nExample: /skill-install jacob-bd/universal-skills-manager", { parseMode: "plain" });
14040
14126
  return;
14041
14127
  }
14042
- await channel.sendText(chatId, `Installing skill from ${commandArgs}...`, "plain");
14128
+ await channel.sendText(chatId, `Installing skill from ${commandArgs}...`, { parseMode: "plain" });
14043
14129
  const result = await installSkillFromGitHub(commandArgs);
14044
14130
  if (result.success) {
14045
14131
  await channel.sendText(chatId, `Skill "${result.skillName}" installed to ~/.cc-claw/workspace/skills/
14046
- Use /skills to see it.`, "plain");
14132
+ Use /skills to see it.`, { parseMode: "plain" });
14047
14133
  } else {
14048
- await channel.sendText(chatId, `Installation failed: ${result.error}`, "plain");
14134
+ await channel.sendText(chatId, `Installation failed: ${result.error}`, { parseMode: "plain" });
14049
14135
  }
14050
14136
  break;
14051
14137
  }
@@ -14079,10 +14165,10 @@ Use /skills to see it.`, "plain");
14079
14165
  }
14080
14166
  } else {
14081
14167
  if (aliases.length === 0) {
14082
- await channel.sendText(chatId, "No chat aliases configured yet.\nUsage: /chats name <chat_id> <alias>\n\nGroup chats are auto-discovered when the bot receives a message from them.", "plain");
14168
+ await channel.sendText(chatId, "No chat aliases configured yet.\nUsage: /chats name <chat_id> <alias>\n\nGroup chats are auto-discovered when the bot receives a message from them.", { parseMode: "plain" });
14083
14169
  } else {
14084
14170
  const lines = ["Authorized chats:", "", ...aliases.map((a) => ` ${a.alias} -> ${a.chatId}`)];
14085
- await channel.sendText(chatId, lines.join("\n"), "plain");
14171
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
14086
14172
  }
14087
14173
  }
14088
14174
  break;
@@ -14092,12 +14178,12 @@ Use /skills to see it.`, "plain");
14092
14178
  const targetChatId = parts[1];
14093
14179
  const alias = parts.slice(2).join("-").toLowerCase();
14094
14180
  setChatAlias(alias, targetChatId);
14095
- await channel.sendText(chatId, `Alias set: ${alias} -> ${targetChatId}`, "plain");
14181
+ await channel.sendText(chatId, `Alias set: ${alias} -> ${targetChatId}`, { parseMode: "plain" });
14096
14182
  } else if (parts[0] === "remove" && parts[1]) {
14097
14183
  const removed = removeChatAlias(parts[1]);
14098
- await channel.sendText(chatId, removed ? `Alias "${parts[1]}" removed.` : `Alias "${parts[1]}" not found.`, "plain");
14184
+ await channel.sendText(chatId, removed ? `Alias "${parts[1]}" removed.` : `Alias "${parts[1]}" not found.`, { parseMode: "plain" });
14099
14185
  } else {
14100
- await channel.sendText(chatId, "Usage:\n /chats \u2014 list all aliases\n /chats name <chat_id> <alias> \u2014 assign alias\n /chats remove <alias> \u2014 remove alias", "plain");
14186
+ await channel.sendText(chatId, "Usage:\n /chats \u2014 list all aliases\n /chats name <chat_id> <alias> \u2014 assign alias\n /chats remove <alias> \u2014 remove alias", { parseMode: "plain" });
14101
14187
  }
14102
14188
  break;
14103
14189
  }
@@ -14111,7 +14197,7 @@ Use /skills to see it.`, "plain");
14111
14197
  if (typeof channel.sendKeyboard === "function") {
14112
14198
  await sendHeartbeatKeyboard(chatId, channel);
14113
14199
  } else {
14114
- await channel.sendText(chatId, "Heartbeat enabled. I'll check in periodically.", "plain");
14200
+ await channel.sendText(chatId, "Heartbeat enabled. I'll check in periodically.", { parseMode: "plain" });
14115
14201
  }
14116
14202
  break;
14117
14203
  }
@@ -14121,14 +14207,14 @@ Use /skills to see it.`, "plain");
14121
14207
  if (typeof channel.sendKeyboard === "function") {
14122
14208
  await sendHeartbeatKeyboard(chatId, channel);
14123
14209
  } else {
14124
- await channel.sendText(chatId, "Heartbeat disabled.", "plain");
14210
+ await channel.sendText(chatId, "Heartbeat disabled.", { parseMode: "plain" });
14125
14211
  }
14126
14212
  break;
14127
14213
  }
14128
14214
  case "interval": {
14129
14215
  const ms = parseIntervalToMs(value ?? "30m");
14130
14216
  if (!ms || ms < 6e4) {
14131
- await channel.sendText(chatId, "Invalid interval. Use e.g. 15m, 30m, 1h, 2h", "plain");
14217
+ await channel.sendText(chatId, "Invalid interval. Use e.g. 15m, 30m, 1h, 2h", { parseMode: "plain" });
14132
14218
  break;
14133
14219
  }
14134
14220
  setHeartbeatConfig(chatId, { intervalMs: ms });
@@ -14138,14 +14224,14 @@ Use /skills to see it.`, "plain");
14138
14224
  if (typeof channel.sendKeyboard === "function") {
14139
14225
  await sendHeartbeatKeyboard(chatId, channel);
14140
14226
  } else {
14141
- await channel.sendText(chatId, `Heartbeat interval set to ${ms / 6e4} minutes.`, "plain");
14227
+ await channel.sendText(chatId, `Heartbeat interval set to ${ms / 6e4} minutes.`, { parseMode: "plain" });
14142
14228
  }
14143
14229
  break;
14144
14230
  }
14145
14231
  case "hours": {
14146
14232
  const hourMatch = (value ?? "").match(/^(\d{1,2})-(\d{1,2})$/);
14147
14233
  if (!hourMatch) {
14148
- await channel.sendText(chatId, "Usage: /heartbeat hours 8-22", "plain");
14234
+ await channel.sendText(chatId, "Usage: /heartbeat hours 8-22", { parseMode: "plain" });
14149
14235
  break;
14150
14236
  }
14151
14237
  const hbStart = `${hourMatch[1].padStart(2, "0")}:00`;
@@ -14154,7 +14240,7 @@ Use /skills to see it.`, "plain");
14154
14240
  if (typeof channel.sendKeyboard === "function") {
14155
14241
  await sendHeartbeatKeyboard(chatId, channel);
14156
14242
  } else {
14157
- await channel.sendText(chatId, `Active hours: ${hbStart} - ${hbEnd}`, "plain");
14243
+ await channel.sendText(chatId, `Active hours: ${hbStart} - ${hbEnd}`, { parseMode: "plain" });
14158
14244
  }
14159
14245
  break;
14160
14246
  }
@@ -14162,7 +14248,7 @@ Use /skills to see it.`, "plain");
14162
14248
  if (typeof channel.sendKeyboard === "function") {
14163
14249
  await sendHeartbeatKeyboard(chatId, channel);
14164
14250
  } else {
14165
- await channel.sendText(chatId, formatHeartbeatStatus(chatId), "plain");
14251
+ await channel.sendText(chatId, formatHeartbeatStatus(chatId), { parseMode: "plain" });
14166
14252
  }
14167
14253
  }
14168
14254
  break;
@@ -14170,7 +14256,7 @@ Use /skills to see it.`, "plain");
14170
14256
  if (typeof channel.sendKeyboard === "function") {
14171
14257
  await sendHeartbeatKeyboard(chatId, channel);
14172
14258
  } else {
14173
- await channel.sendText(chatId, formatHeartbeatStatus(chatId), "plain");
14259
+ await channel.sendText(chatId, formatHeartbeatStatus(chatId), { parseMode: "plain" });
14174
14260
  }
14175
14261
  break;
14176
14262
  }
@@ -14185,7 +14271,7 @@ Use /skills to see it.`, "plain");
14185
14271
  } catch {
14186
14272
  }
14187
14273
  clearSession(chatId);
14188
- await channel.sendText(chatId, `Agent mode set to <b>${modeArg}</b>. Session cleared.`, "html");
14274
+ await channel.sendText(chatId, `Agent mode set to <b>${modeArg}</b>. Session cleared.`, { parseMode: "html" });
14189
14275
  } else if (typeof channel.sendKeyboard === "function") {
14190
14276
  const current = getAgentMode(chatId);
14191
14277
  await channel.sendKeyboard(chatId, `Agent mode: <b>${current}</b>
@@ -14199,7 +14285,7 @@ Choose a mode:`, [
14199
14285
  ]);
14200
14286
  } else {
14201
14287
  const current = getAgentMode(chatId);
14202
- await channel.sendText(chatId, `Agent mode: ${current}. Use /agents mode <auto|native|claw> to change.`, "plain");
14288
+ await channel.sendText(chatId, `Agent mode: ${current}. Use /agents mode <auto|native|claw> to change.`, { parseMode: "plain" });
14203
14289
  }
14204
14290
  return;
14205
14291
  }
@@ -14212,7 +14298,7 @@ Choose a mode:`, [
14212
14298
  ORDER BY createdAt DESC LIMIT 20
14213
14299
  `).all(chatId);
14214
14300
  if (events.length === 0) {
14215
- await channel.sendText(chatId, "No native sub-agent activity in the last 24h.", "plain");
14301
+ await channel.sendText(chatId, "No native sub-agent activity in the last 24h.", { parseMode: "plain" });
14216
14302
  } else {
14217
14303
  const lines2 = events.map((e) => {
14218
14304
  const d = e.detail ? JSON.parse(e.detail) : {};
@@ -14220,7 +14306,7 @@ Choose a mode:`, [
14220
14306
  });
14221
14307
  await channel.sendText(chatId, `<b>Native agent history (24h)</b>
14222
14308
 
14223
- ${lines2.join("\n")}`, "html");
14309
+ ${lines2.join("\n")}`, { parseMode: "html" });
14224
14310
  }
14225
14311
  return;
14226
14312
  }
@@ -14237,7 +14323,7 @@ ${lines2.join("\n")}`, "html");
14237
14323
  const runners2 = getAllRunners();
14238
14324
  const runnerMap = new Map(runners2.map((r) => [r.id, r]));
14239
14325
  if (agents2.length === 0) {
14240
- await channel.sendText(chatId, "<b>Active Agents</b>\n\nNo active agents.", "html");
14326
+ await channel.sendText(chatId, "<b>Active Agents</b>\n\nNo active agents.", { parseMode: "html" });
14241
14327
  break;
14242
14328
  }
14243
14329
  const { getAllAdapters: getAllAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
@@ -14271,7 +14357,7 @@ ${lines2.join("\n")}`, "html");
14271
14357
  lines.push("");
14272
14358
  }
14273
14359
  lines.push(`Total: ${agents2.length} agent(s) (${runningCount} running, ${queuedCount} queued)`);
14274
- await channel.sendText(chatId, lines.join("\n"), "html");
14360
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "html" });
14275
14361
  if (typeof channel.sendKeyboard === "function") {
14276
14362
  const agentButtons = [];
14277
14363
  for (const a of agents2) {
@@ -14290,7 +14376,7 @@ ${lines2.join("\n")}`, "html");
14290
14376
  const db3 = getDb();
14291
14377
  const orch = getActiveOrchestration(db3, chatId);
14292
14378
  if (!orch) {
14293
- await channel.sendText(chatId, "<b>Task Board</b>\n\nNo active orchestration. Start a task to create one.", "html");
14379
+ await channel.sendText(chatId, "<b>Task Board</b>\n\nNo active orchestration. Start a task to create one.", { parseMode: "html" });
14294
14380
  break;
14295
14381
  }
14296
14382
  const tasks = listTasksByOrchestration(db3, orch.id);
@@ -14325,7 +14411,7 @@ ${lines2.join("\n")}`, "html");
14325
14411
  if (lines.length === 2) {
14326
14412
  lines.push("No tasks yet.");
14327
14413
  }
14328
- await channel.sendText(chatId, lines.join("\n").trimEnd(), "html");
14414
+ await channel.sendText(chatId, lines.join("\n").trimEnd(), { parseMode: "html" });
14329
14415
  if (typeof channel.sendKeyboard === "function" && tasks.length > 0) {
14330
14416
  const taskButtons = [];
14331
14417
  const viewable = tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
@@ -14346,7 +14432,7 @@ ${lines2.join("\n")}`, "html");
14346
14432
  const db4 = getDb();
14347
14433
  const agents3 = listActiveAgents(db4);
14348
14434
  if (agents3.length === 0) {
14349
- await channel.sendText(chatId, "No active agents to stop.", "plain");
14435
+ await channel.sendText(chatId, "No active agents to stop.", { parseMode: "plain" });
14350
14436
  break;
14351
14437
  }
14352
14438
  if (typeof channel.sendKeyboard === "function") {
@@ -14374,7 +14460,7 @@ ${lines2.join("\n")}`, "html");
14374
14460
 
14375
14461
  ${agentLines.join("\n")}`, buttons);
14376
14462
  } else {
14377
- await channel.sendText(chatId, "Usage: /stopagent <id>\nUse first 8 chars of agent ID or full ID.", "plain");
14463
+ await channel.sendText(chatId, "Usage: /stopagent <id>\nUse first 8 chars of agent ID or full ID.", { parseMode: "plain" });
14378
14464
  }
14379
14465
  break;
14380
14466
  }
@@ -14383,16 +14469,16 @@ ${agentLines.join("\n")}`, buttons);
14383
14469
  const id = commandArgs.trim();
14384
14470
  const match = agents2.find((a) => a.id === id || a.id.startsWith(id));
14385
14471
  if (!match) {
14386
- await channel.sendText(chatId, `No active agent found matching "${id}". Use /agents to list.`, "plain");
14472
+ await channel.sendText(chatId, `No active agent found matching "${id}". Use /agents to list.`, { parseMode: "plain" });
14387
14473
  break;
14388
14474
  }
14389
14475
  const ok = cancelAgent(match.id);
14390
- await channel.sendText(chatId, ok ? `Agent ${match.id.slice(0, 8)} cancelled.` : "Could not cancel agent.", "plain");
14476
+ await channel.sendText(chatId, ok ? `Agent ${match.id.slice(0, 8)} cancelled.` : "Could not cancel agent.", { parseMode: "plain" });
14391
14477
  break;
14392
14478
  }
14393
14479
  case "stopall": {
14394
14480
  const count = cancelAllAgents(chatId);
14395
- await channel.sendText(chatId, count > 0 ? `Cancelled ${count} agent(s).` : "No active agents to cancel.", "plain");
14481
+ await channel.sendText(chatId, count > 0 ? `Cancelled ${count} agent(s).` : "No active agents to cancel.", { parseMode: "plain" });
14396
14482
  if (count > 0 && typeof channel.sendKeyboard === "function") {
14397
14483
  await channel.sendKeyboard(chatId, "", [
14398
14484
  [
@@ -14406,7 +14492,7 @@ ${agentLines.join("\n")}`, buttons);
14406
14492
  case "runners": {
14407
14493
  const runners2 = getAllRunners();
14408
14494
  if (runners2.length === 0) {
14409
- await channel.sendText(chatId, "<b>Registered Runners</b>\n\nNo runners registered.", "html");
14495
+ await channel.sendText(chatId, "<b>Registered Runners</b>\n\nNo runners registered.", { parseMode: "html" });
14410
14496
  break;
14411
14497
  }
14412
14498
  const lines = ["<b>Registered Runners</b>", ""];
@@ -14414,7 +14500,7 @@ ${agentLines.join("\n")}`, buttons);
14414
14500
  const specialties = r.capabilities.specialties?.join(", ") ?? "";
14415
14501
  lines.push(`\u2022 <b>${r.id}</b> (${r.displayName}) \u2014 ${specialties}`);
14416
14502
  }
14417
- await channel.sendText(chatId, lines.join("\n"), "html");
14503
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "html" });
14418
14504
  break;
14419
14505
  }
14420
14506
  case "mcp":
@@ -14483,7 +14569,7 @@ ${agentLines.join("\n")}`, buttons);
14483
14569
  } else {
14484
14570
  lines.splice(1, 0, `<i>${connectedCount}/${totalCount} connected</i>`);
14485
14571
  }
14486
- await channel.sendText(chatId, lines.join("\n"), "html");
14572
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "html" });
14487
14573
  break;
14488
14574
  }
14489
14575
  case "cron": {
@@ -14502,7 +14588,7 @@ ${agentLines.join("\n")}`, buttons);
14502
14588
  }
14503
14589
  const id = parseInt(cronSubArgs, 10);
14504
14590
  const ok = cancelJob(id);
14505
- await channel.sendText(chatId, ok ? `Job #${id} cancelled.` : `Job #${id} not found.`, "plain");
14591
+ await channel.sendText(chatId, ok ? `Job #${id} cancelled.` : `Job #${id} not found.`, { parseMode: "plain" });
14506
14592
  break;
14507
14593
  }
14508
14594
  case "pause": {
@@ -14512,7 +14598,7 @@ ${agentLines.join("\n")}`, buttons);
14512
14598
  }
14513
14599
  const pauseId = parseInt(cronSubArgs, 10);
14514
14600
  const paused = pauseJob(pauseId);
14515
- await channel.sendText(chatId, paused ? `Job #${pauseId} paused.` : `Job #${pauseId} not found.`, "plain");
14601
+ await channel.sendText(chatId, paused ? `Job #${pauseId} paused.` : `Job #${pauseId} not found.`, { parseMode: "plain" });
14516
14602
  break;
14517
14603
  }
14518
14604
  case "resume": {
@@ -14522,7 +14608,7 @@ ${agentLines.join("\n")}`, buttons);
14522
14608
  }
14523
14609
  const resumeId = parseInt(cronSubArgs, 10);
14524
14610
  const resumed = resumeJob(resumeId);
14525
- await channel.sendText(chatId, resumed ? `Job #${resumeId} resumed.` : `Job #${resumeId} not found.`, "plain");
14611
+ await channel.sendText(chatId, resumed ? `Job #${resumeId} resumed.` : `Job #${resumeId} not found.`, { parseMode: "plain" });
14526
14612
  break;
14527
14613
  }
14528
14614
  case "run": {
@@ -14531,9 +14617,9 @@ ${agentLines.join("\n")}`, buttons);
14531
14617
  return;
14532
14618
  }
14533
14619
  const runId = parseInt(cronSubArgs, 10);
14534
- await channel.sendText(chatId, `Triggering job #${runId}...`, "plain");
14620
+ await channel.sendText(chatId, `Triggering job #${runId}...`, { parseMode: "plain" });
14535
14621
  const runResult = await triggerJob(runId);
14536
- await channel.sendText(chatId, runResult, "plain");
14622
+ await channel.sendText(chatId, runResult, { parseMode: "plain" });
14537
14623
  break;
14538
14624
  }
14539
14625
  case "runs": {
@@ -14543,20 +14629,20 @@ ${agentLines.join("\n")}`, buttons);
14543
14629
  } else {
14544
14630
  const cronRuns2 = getJobRuns(void 0, 10);
14545
14631
  if (cronRuns2.length === 0) {
14546
- await channel.sendText(chatId, "No run history.", "plain");
14632
+ await channel.sendText(chatId, "No run history.", { parseMode: "plain" });
14547
14633
  return;
14548
14634
  }
14549
14635
  const runLines = cronRuns2.map((r) => {
14550
14636
  const dur = r.durationMs ? ` (${(r.durationMs / 1e3).toFixed(1)}s)` : "";
14551
14637
  return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${dur}`;
14552
14638
  });
14553
- await channel.sendText(chatId, runLines.join("\n\n"), "plain");
14639
+ await channel.sendText(chatId, runLines.join("\n\n"), { parseMode: "plain" });
14554
14640
  }
14555
14641
  break;
14556
14642
  }
14557
14643
  case "edit": {
14558
14644
  if (!cronSubArgs) {
14559
- await channel.sendText(chatId, "Usage: /cron edit <id>", "plain");
14645
+ await channel.sendText(chatId, "Usage: /cron edit <id>", { parseMode: "plain" });
14560
14646
  return;
14561
14647
  }
14562
14648
  const editId = parseInt(cronSubArgs, 10);
@@ -14565,7 +14651,7 @@ ${agentLines.join("\n")}`, buttons);
14565
14651
  }
14566
14652
  case "health": {
14567
14653
  const report = getHealthReport();
14568
- await channel.sendText(chatId, formatHealthReport(report), "plain");
14654
+ await channel.sendText(chatId, formatHealthReport(report), { parseMode: "plain" });
14569
14655
  break;
14570
14656
  }
14571
14657
  default:
@@ -14577,84 +14663,112 @@ ${agentLines.join("\n")}`, buttons);
14577
14663
  const testMsg = commandArgs?.trim() || "hey";
14578
14664
  const result = classifyIntent(testMsg, chatId);
14579
14665
  await channel.sendText(chatId, `Intent: ${result}
14580
- Message: "${testMsg}"`, "plain");
14666
+ Message: "${testMsg}"`, { parseMode: "plain" });
14581
14667
  break;
14582
14668
  }
14583
14669
  case "evolve": {
14584
- const { buildEvolveMenuKeyboard: buildEvolveMenuKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14585
- const keyboard = buildEvolveMenuKeyboard2();
14586
- await channel.sendKeyboard(chatId, "Self-Learning & Evolution", keyboard);
14587
- break;
14588
- }
14589
- case "reflect": {
14590
- if (typeof channel.sendKeyboard === "function") {
14591
- const { getUnprocessedSignalCount: getUnprocessedSignalCount2, getLastAnalysisTime: getLastAnalysisTime2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14592
- const signalCount = getUnprocessedSignalCount2(getDb(), chatId);
14670
+ const { getReflectionStatus: getRefStatus, getUnprocessedSignalCount: getUnprocessedSignalCount2, getPendingInsightCount: getPendingInsightCount2, getLastAnalysisTime: getLastAnalysisTime2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14671
+ const isActive = getRefStatus(getDb(), chatId) === "active";
14672
+ if (!isActive) {
14673
+ const text = [
14674
+ "Self-Learning & Evolution",
14675
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
14676
+ "",
14677
+ "Teach your assistant to improve over time.",
14678
+ "",
14679
+ "When enabled, CC-Claw watches for corrections,",
14680
+ "preferences, and frustration in your messages,",
14681
+ "then proposes changes to its personality and",
14682
+ "behavior files (SOUL.md, USER.md).",
14683
+ "",
14684
+ "You review and approve every change."
14685
+ ].join("\n");
14686
+ if (typeof channel.sendKeyboard === "function") {
14687
+ const { buildEvolveOnboardingKeyboard: buildEvolveOnboardingKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14688
+ await channel.sendKeyboard(chatId, text, buildEvolveOnboardingKeyboard2());
14689
+ } else {
14690
+ await channel.sendText(chatId, text + "\n\nUse /evolve enable to activate.", { parseMode: "plain" });
14691
+ }
14692
+ } else {
14693
+ const signals = getUnprocessedSignalCount2(getDb(), chatId);
14694
+ const pending = getPendingInsightCount2(getDb(), chatId);
14593
14695
  const lastTime = getLastAnalysisTime2(getDb(), chatId);
14594
- let lastAnalysisText = "never";
14696
+ let lastText = "never";
14595
14697
  if (lastTime) {
14596
14698
  const diffMs = Date.now() - (/* @__PURE__ */ new Date(lastTime + "Z")).getTime();
14597
14699
  const diffHours = Math.floor(diffMs / 36e5);
14598
- if (diffHours < 1) lastAnalysisText = "less than an hour ago";
14599
- else if (diffHours < 24) lastAnalysisText = `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`;
14600
- else {
14601
- const diffDays = Math.floor(diffHours / 24);
14602
- lastAnalysisText = `${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
14603
- }
14700
+ if (diffHours < 1) lastText = "< 1 hour ago";
14701
+ else if (diffHours < 24) lastText = `${diffHours}h ago`;
14702
+ else lastText = `${Math.floor(diffHours / 24)}d ago`;
14604
14703
  }
14605
- const text = [
14606
- "Reflection Analysis",
14704
+ const dashText = [
14705
+ "Self-Learning & Evolution",
14607
14706
  "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
14608
14707
  "",
14609
- "This will analyze recent interactions for",
14610
- "improvement insights. Uses the summarizer model.",
14611
- "",
14612
- `Unprocessed signals: ${signalCount}`,
14613
- `Last analysis: ${lastAnalysisText}`
14708
+ `\u2705 Active`,
14709
+ `Signals: ${signals} pending \xB7 Proposals: ${pending}`,
14710
+ `Last analysis: ${lastText}`
14614
14711
  ].join("\n");
14615
- await channel.sendKeyboard(chatId, text, [[
14616
- { label: "Analyze Now", data: "reflect:go", style: "success" },
14617
- { label: "Cancel", data: "reflect:cancel" }
14618
- ]]);
14619
- } else {
14620
- const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
14621
- const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14622
- await channel.sendText(chatId, "Analyzing recent interactions...", "plain");
14623
- try {
14624
- const insights = await runAnalysis2(chatId);
14625
- if (insights.length === 0) {
14626
- await channel.sendText(chatId, "No new insights from recent interactions.", "plain");
14712
+ if (typeof channel.sendKeyboard === "function") {
14713
+ const { buildEvolveMenuKeyboard: buildEvolveMenuKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14714
+ await channel.sendKeyboard(chatId, dashText, buildEvolveMenuKeyboard2({ pendingProposals: pending, unprocessedSignals: signals }));
14715
+ } else {
14716
+ await channel.sendText(chatId, dashText, { parseMode: "plain" });
14717
+ }
14718
+ }
14719
+ break;
14720
+ }
14721
+ case "reflect": {
14722
+ const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
14723
+ const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14724
+ await channel.sendText(chatId, "Analyzing recent interactions...", { parseMode: "plain" });
14725
+ try {
14726
+ const insights = await runAnalysis2(chatId, { force: true });
14727
+ if (insights.length === 0) {
14728
+ const { getReflectionStatus: getRefStatus } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14729
+ const isFrozen = getRefStatus(getDb(), chatId) === "frozen";
14730
+ if (isFrozen) {
14731
+ const msg2 = "No insights found. Self-learning is disabled \u2014\nenable it with /evolve so feedback signals are\ncaptured automatically between analyses.";
14732
+ if (typeof channel.sendKeyboard === "function") {
14733
+ await channel.sendKeyboard(chatId, msg2, [[
14734
+ { label: "Open /evolve", data: "evolve:menu", style: "primary" }
14735
+ ]]);
14736
+ } else {
14737
+ await channel.sendText(chatId, msg2, { parseMode: "plain" });
14738
+ }
14627
14739
  } else {
14628
- const items = insights.map((ins, i) => ({ id: i + 1, category: ins.category, insight: ins.insight }));
14629
- await channel.sendText(chatId, formatNightlySummary2(items), "plain");
14740
+ await channel.sendText(chatId, "No new insights from recent interactions.", { parseMode: "plain" });
14630
14741
  }
14631
- } catch (e) {
14632
- await channel.sendText(chatId, `Analysis failed: ${e}`, "plain");
14742
+ } else {
14743
+ const items = insights.map((ins, i) => ({ id: i + 1, category: ins.category, insight: ins.insight }));
14744
+ await channel.sendText(chatId, formatNightlySummary2(items) + "\n\nUse /evolve to review and apply proposals.", { parseMode: "plain" });
14633
14745
  }
14746
+ } catch (e) {
14747
+ await channel.sendText(chatId, `Analysis failed: ${e}`, { parseMode: "plain" });
14634
14748
  }
14635
14749
  break;
14636
14750
  }
14637
14751
  default:
14638
- await channel.sendText(chatId, `Unknown command: /${command}. Type /help for available commands.`, "plain");
14752
+ await channel.sendText(chatId, `Unknown command: /${command}. Type /help for available commands.`, { parseMode: "plain" });
14639
14753
  }
14640
14754
  }
14641
14755
  async function handleVoice(msg, channel) {
14642
14756
  const { chatId, fileName } = msg;
14643
- await channel.sendText(chatId, "Transcribing voice message...", "plain");
14757
+ await channel.sendText(chatId, "Transcribing voice message...", { parseMode: "plain" });
14644
14758
  try {
14645
14759
  const audioBuffer = await channel.downloadFile(fileName);
14646
14760
  const transcript = await transcribeAudio(audioBuffer);
14647
14761
  if (!transcript) {
14648
- await channel.sendText(chatId, "Couldn't transcribe the voice message.", "plain");
14762
+ await channel.sendText(chatId, "Couldn't transcribe the voice message.", { parseMode: "plain" });
14649
14763
  return;
14650
14764
  }
14651
14765
  const vBackendId = getBackend(chatId) ?? "claude";
14652
14766
  const vLimitMsg = checkBackendLimits(vBackendId);
14653
14767
  if (vLimitMsg) {
14654
- await channel.sendText(chatId, vLimitMsg, "plain");
14768
+ await channel.sendText(chatId, vLimitMsg, { parseMode: "plain" });
14655
14769
  return;
14656
14770
  }
14657
- await channel.sendText(chatId, "Thinking...", "plain");
14771
+ await channel.sendText(chatId, "Thinking...", { parseMode: "plain" });
14658
14772
  const mode = getMode(chatId);
14659
14773
  const vModel = resolveModel(chatId);
14660
14774
  const vVerbose = getVerboseLevel(chatId);
@@ -14666,7 +14780,7 @@ async function handleVoice(msg, channel) {
14666
14780
  await sendResponse(chatId, channel, voiceResponse, msg.messageId);
14667
14781
  } catch (err) {
14668
14782
  error("[router] Voice error:", err);
14669
- await channel.sendText(chatId, `Voice processing error: ${errorMessage(err)}`, "plain");
14783
+ await channel.sendText(chatId, `Voice processing error: ${errorMessage(err)}`, { parseMode: "plain" });
14670
14784
  }
14671
14785
  }
14672
14786
  async function handleMedia(msg, channel) {
@@ -14674,15 +14788,15 @@ async function handleMedia(msg, channel) {
14674
14788
  const mBackendId = getBackend(chatId) ?? "claude";
14675
14789
  const mLimitMsg = checkBackendLimits(mBackendId);
14676
14790
  if (mLimitMsg) {
14677
- await channel.sendText(chatId, mLimitMsg, "plain");
14791
+ await channel.sendText(chatId, mLimitMsg, { parseMode: "plain" });
14678
14792
  return;
14679
14793
  }
14680
- await channel.sendText(chatId, "Processing your file...", "plain");
14794
+ await channel.sendText(chatId, "Processing your file...", { parseMode: "plain" });
14681
14795
  try {
14682
14796
  if (msg.type === "video") {
14683
14797
  const fileId = msg.metadata?.fileId ?? fileName;
14684
14798
  if (!fileId) {
14685
- await channel.sendText(chatId, "Could not retrieve video file ID.", "plain");
14799
+ await channel.sendText(chatId, "Could not retrieve video file ID.", { parseMode: "plain" });
14686
14800
  return;
14687
14801
  }
14688
14802
  const videoBuffer = await channel.downloadFile(fileId);
@@ -14774,11 +14888,12 @@ ${content}
14774
14888
  await sendResponse(chatId, channel, mediaResponse, msg.messageId);
14775
14889
  } catch (err) {
14776
14890
  error("[router] Media error:", err);
14777
- await channel.sendText(chatId, `Error processing file: ${errorMessage(err)}`, "plain");
14891
+ await channel.sendText(chatId, `Error processing file: ${errorMessage(err)}`, { parseMode: "plain" });
14778
14892
  }
14779
14893
  }
14780
14894
  async function handleText(msg, channel) {
14781
- const { chatId, text } = msg;
14895
+ const { chatId } = msg;
14896
+ let { text } = msg;
14782
14897
  if (hasActiveProfile(chatId)) {
14783
14898
  await handleProfileText(chatId, text, channel);
14784
14899
  return;
@@ -14786,7 +14901,7 @@ async function handleText(msg, channel) {
14786
14901
  if (hasPendingWizard(chatId)) {
14787
14902
  if (text.toLowerCase() === "cancel") {
14788
14903
  cancelWizard(chatId);
14789
- await channel.sendText(chatId, "Scheduling cancelled.", "plain");
14904
+ await channel.sendText(chatId, "Scheduling cancelled.", { parseMode: "plain" });
14790
14905
  return;
14791
14906
  }
14792
14907
  if (text.toLowerCase() === "confirm") {
@@ -14796,19 +14911,39 @@ async function handleText(msg, channel) {
14796
14911
  await handleWizardText(chatId, text, channel);
14797
14912
  return;
14798
14913
  }
14914
+ const discussingInsightId = activeProposalDiscussion.get(chatId);
14915
+ if (discussingInsightId !== void 0) {
14916
+ if (text.toLowerCase() === "done") {
14917
+ activeProposalDiscussion.delete(chatId);
14918
+ const { getInsightById: getInsightById2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14919
+ const { formatProposalCard: formatProposalCard2, buildProposalKeyboard: buildProposalKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14920
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
14921
+ const insight = getInsightById2(getDb2(), discussingInsightId);
14922
+ if (insight && insight.status === "pending") {
14923
+ const card = formatProposalCard2(insight);
14924
+ const kb = buildProposalKeyboard2(insight.id, insight.category);
14925
+ await channel.sendKeyboard(chatId, "Discussion complete. Updated proposal:\n\n" + card, kb);
14926
+ } else {
14927
+ await channel.sendText(chatId, "Discussion ended.", { parseMode: "plain" });
14928
+ }
14929
+ return;
14930
+ }
14931
+ await handleProposalDiscussion(chatId, discussingInsightId, text, channel);
14932
+ return;
14933
+ }
14799
14934
  const rememberMatch = text.match(/^remember\s+(?:that\s+)?(.+)/i);
14800
14935
  if (rememberMatch) {
14801
14936
  const content = rememberMatch[1];
14802
14937
  const trigger = content.split(/\s+/).slice(0, 3).join(" ");
14803
14938
  saveMemoryWithEmbedding(trigger, content, "semantic");
14804
- await channel.sendText(chatId, "Got it, I'll remember that.", "plain");
14939
+ await channel.sendText(chatId, "Got it, I'll remember that.", { parseMode: "plain" });
14805
14940
  return;
14806
14941
  }
14807
14942
  const model2 = resolveModel(chatId);
14808
14943
  const backendId = getBackend(chatId) ?? "claude";
14809
14944
  const limitMsg = checkBackendLimits(backendId);
14810
14945
  if (limitMsg) {
14811
- await channel.sendText(chatId, limitMsg, "plain");
14946
+ await channel.sendText(chatId, limitMsg, { parseMode: "plain" });
14812
14947
  return;
14813
14948
  }
14814
14949
  let intent = classifyIntent(text, chatId);
@@ -14830,18 +14965,32 @@ async function handleText(msg, channel) {
14830
14965
  const lastWarn = dashboardClawWarnings.get(chatId) ?? 0;
14831
14966
  if (Date.now() - lastWarn > 3e5) {
14832
14967
  dashboardClawWarnings.set(chatId, Date.now());
14833
- await channel.sendText(chatId, "\u26A0\uFE0F CC-Claw orchestration requires DASHBOARD_ENABLED=1. Using native agents.", "plain");
14968
+ await channel.sendText(chatId, "\u26A0\uFE0F CC-Claw orchestration requires DASHBOARD_ENABLED=1. Using native agents.", { parseMode: "plain" });
14834
14969
  }
14835
14970
  effectiveAgentMode = "native";
14836
14971
  }
14972
+ const { isSideQuest: hasSqPrefix, cleanText: sqCleanText } = parseSideQuestPrefix(text);
14973
+ if (hasSqPrefix && isChatBusy(chatId)) {
14974
+ const sqMsg = { ...msg, text: sqCleanText };
14975
+ handleSideQuest(chatId, sqMsg, channel).catch(
14976
+ (err) => error(`[router] Side quest error for ${chatId}:`, err)
14977
+ );
14978
+ return;
14979
+ }
14980
+ if (hasSqPrefix) {
14981
+ text = sqCleanText;
14982
+ }
14837
14983
  if (isChatBusy(chatId) && !bypassBusyCheck.delete(chatId)) {
14838
14984
  if (typeof channel.sendKeyboard === "function") {
14839
14985
  pendingInterrupts.set(chatId, { msg, channel });
14840
- await channel.sendKeyboard(chatId, "\u23F3 Agent is working on a request\u2026", [
14986
+ await channel.sendKeyboard(chatId, "\u23F3 Agent is working on a request. You can queue, interrupt, or start a parallel task.", [
14841
14987
  [
14842
- { label: "\u{1F4E5} Queue message", data: `interrupt:queue:${chatId}` },
14988
+ { label: "\u{1F4E5} Queue", data: `interrupt:queue:${chatId}` },
14843
14989
  { label: "\u26A1 Send now", data: `interrupt:now:${chatId}` }
14844
14990
  ],
14991
+ [
14992
+ { label: "\u{1F5FA} Side quest \u2014 run in parallel", data: `interrupt:sidequest:${chatId}` }
14993
+ ],
14845
14994
  [
14846
14995
  { label: "\u{1F5D1} Don't send", data: `interrupt:discard:${chatId}` }
14847
14996
  ]
@@ -14897,7 +15046,7 @@ async function handleText(msg, channel) {
14897
15046
  const toSlot = slots.find((s) => (s.label || `slot-${s.id}`) === to);
14898
15047
  const fromIcon = fromSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
14899
15048
  const toIcon = toSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
14900
- channel.sendText(cid, `\u26A0\uFE0F Quota reached on ${fromIcon} ${from} \u2014 switched to ${toIcon} ${to}. Context saved.`, "plain").catch(() => {
15049
+ channel.sendText(cid, `\u26A0\uFE0F Quota reached on ${fromIcon} ${from} \u2014 switched to ${toIcon} ${to}. Context saved.`, { parseMode: "plain" }).catch(() => {
14901
15050
  });
14902
15051
  }
14903
15052
  });
@@ -14933,6 +15082,9 @@ async function handleText(msg, channel) {
14933
15082
  return;
14934
15083
  }
14935
15084
  if (await handleResponseExhaustion(responseText, chatId, msg, channel)) return;
15085
+ if (activeSideQuests.has(chatId) && responseText && !responseText.startsWith("(No response")) {
15086
+ responseText = "\u{1F4CB} <b>Main task</b>\n\n" + responseText;
15087
+ }
14936
15088
  responseText = ensureReaction(responseText, cleanText || text);
14937
15089
  await sendResponse(chatId, channel, responseText, msg.messageId);
14938
15090
  try {
@@ -14959,7 +15111,7 @@ async function handleText(msg, channel) {
14959
15111
  ];
14960
15112
  await channel.sendKeyboard(chatId, `\u26A0\uFE0F No eligible slots for rotation mode (${modeLabel}). Switch backend?`, rows);
14961
15113
  } else {
14962
- await channel.sendText(chatId, `\u26A0\uFE0F No eligible slots for rotation mode (${modeLabel}).`, "plain");
15114
+ await channel.sendText(chatId, `\u26A0\uFE0F No eligible slots for rotation mode (${modeLabel}).`, { parseMode: "plain" });
14963
15115
  }
14964
15116
  return;
14965
15117
  } else if (errMsg.includes(GEMINI_ALL_SLOTS_COOLDOWN_MSG)) {
@@ -14971,7 +15123,7 @@ async function handleText(msg, channel) {
14971
15123
  ];
14972
15124
  await channel.sendKeyboard(chatId, `\u26A0\uFE0F All eligible slots in cooldown. Next available in ~${timeLeft}. Switch backend?`, rows);
14973
15125
  } else {
14974
- await channel.sendText(chatId, `\u26A0\uFE0F All eligible slots in cooldown. Next available in ~${timeLeft}.`, "plain");
15126
+ await channel.sendText(chatId, `\u26A0\uFE0F All eligible slots in cooldown. Next available in ~${timeLeft}.`, { parseMode: "plain" });
14975
15127
  }
14976
15128
  return;
14977
15129
  }
@@ -14980,7 +15132,7 @@ async function handleText(msg, channel) {
14980
15132
  if (await handleResponseExhaustion(errMsg, chatId, msg, channel)) return;
14981
15133
  }
14982
15134
  const userMsg = diagnoseAgentError(errMsg, chatId);
14983
- await channel.sendText(chatId, userMsg, "plain");
15135
+ await channel.sendText(chatId, userMsg, { parseMode: "plain" });
14984
15136
  } finally {
14985
15137
  typingActive = false;
14986
15138
  const pending = pendingInterrupts.get(chatId);
@@ -15006,6 +15158,140 @@ After installing, restart the service: cc-claw service restart`;
15006
15158
  }
15007
15159
  return `Error: ${msg}`;
15008
15160
  }
15161
+ async function handleProposalDiscussion(chatId, insightId, userQuestion, channel) {
15162
+ const { getInsightById: getInsightById2, updateInsightProposal: updateInsightProposal2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
15163
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
15164
+ const insight = getInsightById2(getDb2(), insightId);
15165
+ if (!insight) {
15166
+ activeProposalDiscussion.delete(chatId);
15167
+ await channel.sendText(chatId, "Proposal not found. Discussion ended.", { parseMode: "plain" });
15168
+ return;
15169
+ }
15170
+ const prompt = [
15171
+ `You are reviewing a self-learning proposal with the user. They want to discuss it before deciding whether to apply, modify, or reject it.`,
15172
+ ``,
15173
+ `Current proposal:`,
15174
+ `- Category: ${insight.category}`,
15175
+ `- Insight: ${insight.insight}`,
15176
+ `- Why: ${insight.why ?? "not specified"}`,
15177
+ `- Target file: ${insight.targetFile ?? "not specified"}`,
15178
+ `- Proposed action: ${insight.proposedAction ?? "not specified"}`,
15179
+ `- Proposed diff: ${insight.proposedDiff ?? "none"}`,
15180
+ `- Confidence: ${Math.round(insight.confidence * 100)}%`,
15181
+ ``,
15182
+ `The user asks: "${userQuestion}"`,
15183
+ ``,
15184
+ `Answer their question honestly. If they suggest retargeting to a different file or modifying the proposal, agree if it makes sense and output one of these markers:`,
15185
+ `- [RETARGET:filepath] \u2014 to change the target file (e.g., [RETARGET:skills/newsroom/SKILL.md])`,
15186
+ `- [REVISE_DIFF:new diff text] \u2014 to update the proposed change`,
15187
+ `- [REVISE_ACTION:action] \u2014 to change the action type (append/replace/etc.)`,
15188
+ ``,
15189
+ `Only output markers when the user explicitly asks for a change. Otherwise just discuss.`,
15190
+ `Keep responses concise \u2014 this is a quick review conversation, not an essay.`
15191
+ ].join("\n");
15192
+ try {
15193
+ await channel.sendTyping?.(chatId);
15194
+ const response = await askAgent(chatId, prompt, {
15195
+ bootstrapTier: "chat",
15196
+ maxTurns: 1,
15197
+ timeoutMs: 6e4
15198
+ });
15199
+ const responseText = response.text ?? "";
15200
+ const retargetMatch = responseText.match(/\[RETARGET:([^\]]+)\]/);
15201
+ const reviseDiffMatch = responseText.match(/\[REVISE_DIFF:([^\]]+)\]/);
15202
+ const reviseActionMatch = responseText.match(/\[REVISE_ACTION:([^\]]+)\]/);
15203
+ if (retargetMatch || reviseDiffMatch || reviseActionMatch) {
15204
+ const newTarget = retargetMatch?.[1]?.trim() ?? insight.targetFile ?? "";
15205
+ const newDiff = reviseDiffMatch?.[1]?.trim() ?? insight.proposedDiff ?? null;
15206
+ const newAction = reviseActionMatch?.[1]?.trim() ?? insight.proposedAction ?? null;
15207
+ updateInsightProposal2(getDb2(), insightId, newTarget, newDiff, newAction);
15208
+ }
15209
+ const cleanText = responseText.replace(/\[RETARGET:[^\]]+\]/g, "").replace(/\[REVISE_DIFF:[^\]]+\]/g, "").replace(/\[REVISE_ACTION:[^\]]+\]/g, "").trim();
15210
+ if (cleanText) {
15211
+ await channel.sendText(chatId, cleanText);
15212
+ }
15213
+ await channel.sendKeyboard(chatId, "Continue discussing, or type 'done' to finish.", [
15214
+ [{ label: "Done \u2014 show updated proposal", data: `evolve:discuss-done:${insightId}` }]
15215
+ ]);
15216
+ } catch (err) {
15217
+ await channel.sendText(chatId, `Discussion error: ${err.message}`, { parseMode: "plain" });
15218
+ }
15219
+ }
15220
+ async function handleSideQuest(parentChatId, msg, channel) {
15221
+ const sqId = `sq:${parentChatId}:${randomUUID3()}`;
15222
+ const active = activeSideQuests.get(parentChatId) ?? /* @__PURE__ */ new Set();
15223
+ if (active.size >= MAX_SIDE_QUESTS) {
15224
+ await channel.sendText(parentChatId, `Side quest slots full (${active.size} running). Queue this or wait for one to finish.`, { parseMode: "plain" });
15225
+ return;
15226
+ }
15227
+ active.add(sqId);
15228
+ activeSideQuests.set(parentChatId, active);
15229
+ await channel.sendKeyboard?.(parentChatId, "\u{1F5FA} Starting side quest\u2026", [
15230
+ [{ label: "\u274C Cancel", data: `sq:cancel:${sqId}` }]
15231
+ ]);
15232
+ const startTime = Date.now();
15233
+ let typingActive = true;
15234
+ const typingLoop = async () => {
15235
+ while (typingActive) {
15236
+ try {
15237
+ await channel.sendTyping?.(parentChatId);
15238
+ } catch {
15239
+ }
15240
+ await new Promise((r) => setTimeout(r, 4e3));
15241
+ }
15242
+ };
15243
+ typingLoop().catch(() => {
15244
+ });
15245
+ try {
15246
+ const backend2 = getBackend(parentChatId);
15247
+ const model2 = getModel(parentChatId);
15248
+ const permMode = getMode(parentChatId);
15249
+ const response = await askAgent(sqId, msg.text ?? "", {
15250
+ cwd: getCwd(parentChatId) ?? void 0,
15251
+ model: model2 ?? void 0,
15252
+ permMode,
15253
+ backend: backend2 ?? void 0,
15254
+ settingsSourceChatId: parentChatId,
15255
+ agentMode: "native",
15256
+ maxTurns: 10,
15257
+ timeoutMs: 3e5,
15258
+ bootstrapTier: "full"
15259
+ });
15260
+ typingActive = false;
15261
+ const userText = msg.text ?? "";
15262
+ const truncated = userText.length > 60 ? userText.slice(0, 57) + "\u2026" : userText;
15263
+ const header2 = `\u{1F5FA} <b>Side quest: "${truncated}"</b>
15264
+
15265
+ `;
15266
+ const responseText = header2 + (response.text ?? "");
15267
+ await sendResponse(parentChatId, channel, responseText, msg.messageId, typeof msg.messageId === "string" ? parseInt(msg.messageId) : void 0);
15268
+ const adapterForLog = backend2 ? getAdapter(backend2) : getAdapterForChat(parentChatId);
15269
+ appendToLog(parentChatId, `[side quest] ${userText}`, `[side quest] ${response.text ?? ""}`, adapterForLog.id, model2 ?? null, null);
15270
+ if (response.usage) {
15271
+ addUsage(parentChatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2 ?? void 0, backend2 ?? void 0);
15272
+ }
15273
+ try {
15274
+ const { detectAndLogSignals: detectAndLogSignals2 } = await Promise.resolve().then(() => (init_detect(), detect_exports));
15275
+ const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(parentChatId);
15276
+ detectAndLogSignals2(parentChatId, userText, response.text ?? "", {
15277
+ backendId: adapter.id,
15278
+ model: model2 ?? adapter.defaultModel
15279
+ });
15280
+ } catch {
15281
+ }
15282
+ } catch (err) {
15283
+ typingActive = false;
15284
+ await channel.sendText(parentChatId, `\u{1F5FA} Side quest failed: ${err.message}`, { parseMode: "plain" });
15285
+ } finally {
15286
+ typingActive = false;
15287
+ const activeSet = activeSideQuests.get(parentChatId);
15288
+ if (activeSet) {
15289
+ activeSet.delete(sqId);
15290
+ if (activeSet.size === 0) activeSideQuests.delete(parentChatId);
15291
+ }
15292
+ clearSession(sqId);
15293
+ }
15294
+ }
15009
15295
  function formatToolStart(toolName, input, level) {
15010
15296
  if (level === "normal") {
15011
15297
  const path = input.file_path ?? input.path ?? input.file ?? "";
@@ -15078,11 +15364,11 @@ function makeToolActionCallback(chatId, channel, level) {
15078
15364
  return async (toolName, input, result) => {
15079
15365
  if (result === void 0) {
15080
15366
  const msg = formatToolStart(toolName, input, level);
15081
- await channel.sendText(chatId, msg, "plain").catch(() => {
15367
+ await channel.sendText(chatId, msg, { parseMode: "plain" }).catch(() => {
15082
15368
  });
15083
15369
  } else if (level === "verbose") {
15084
15370
  const msg = formatToolResult(toolName, result);
15085
- if (msg) await channel.sendText(chatId, msg, "plain").catch(() => {
15371
+ if (msg) await channel.sendText(chatId, msg, { parseMode: "plain" }).catch(() => {
15086
15372
  });
15087
15373
  }
15088
15374
  };
@@ -15133,11 +15419,11 @@ async function processImageGenerations(chatId, channel, text) {
15133
15419
  const name = result.filePath.split("/").pop() ?? "image.png";
15134
15420
  await channel.sendFile(chatId, file, name);
15135
15421
  if (result.text) {
15136
- await channel.sendText(chatId, result.text, "plain");
15422
+ await channel.sendText(chatId, result.text, { parseMode: "plain" });
15137
15423
  }
15138
15424
  } catch (err) {
15139
15425
  error(`[router] Image generation failed for "${prompt.slice(0, 50)}":`, err);
15140
- await channel.sendText(chatId, `Image generation failed: ${errorMessage(err)}`, "plain");
15426
+ await channel.sendText(chatId, `Image generation failed: ${errorMessage(err)}`, { parseMode: "plain" });
15141
15427
  }
15142
15428
  }
15143
15429
  return text.replace(pattern, "").trim();
@@ -15164,7 +15450,7 @@ async function processReaction(chatId, channel, text, messageId) {
15164
15450
  }
15165
15451
  return text.replace(/\[REACT:(.+?)\]/g, "").trim();
15166
15452
  }
15167
- async function sendResponse(chatId, channel, text, messageId) {
15453
+ async function sendResponse(chatId, channel, text, messageId, replyToMessageId) {
15168
15454
  text = await processReaction(chatId, channel, text, messageId);
15169
15455
  const { cleanText: afterUpdates, updates } = extractUserUpdates(text);
15170
15456
  for (const { key, value } of updates) {
@@ -15209,14 +15495,14 @@ async function sendResponse(chatId, channel, text, messageId) {
15209
15495
  error("[router] TTS failed, falling back to text:", err);
15210
15496
  }
15211
15497
  }
15212
- await channel.sendText(chatId, cleanText);
15498
+ await channel.sendText(chatId, cleanText, replyToMessageId ? { replyToMessageId } : void 0);
15213
15499
  }
15214
15500
  function isImageExt(ext) {
15215
15501
  return ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(ext);
15216
15502
  }
15217
15503
  async function sendVoiceConfigKeyboard(chatId, channel) {
15218
15504
  if (typeof channel.sendKeyboard !== "function") {
15219
- await channel.sendText(chatId, "Voice configuration requires an interactive channel (Telegram).", "plain");
15505
+ await channel.sendText(chatId, "Voice configuration requires an interactive channel (Telegram).", { parseMode: "plain" });
15220
15506
  return;
15221
15507
  }
15222
15508
  const config2 = getVoiceConfig(chatId);
@@ -15282,7 +15568,7 @@ async function sendBackendSwitchConfirmation(chatId, target, channel) {
15282
15568
  const current = getBackend(chatId);
15283
15569
  const targetAdapter = getAdapter(target);
15284
15570
  if (current === target) {
15285
- await channel.sendText(chatId, `Already using ${targetAdapter.displayName}.`, "plain");
15571
+ await channel.sendText(chatId, `Already using ${targetAdapter.displayName}.`, { parseMode: "plain" });
15286
15572
  return;
15287
15573
  }
15288
15574
  if (typeof channel.sendKeyboard === "function") {
@@ -15306,7 +15592,7 @@ async function doBackendSwitch(chatId, backendId, channel) {
15306
15592
  const targetAdapter = getAdapter(backendId);
15307
15593
  const pairCount = getMessagePairCount(chatId);
15308
15594
  if (pairCount >= 2) {
15309
- await channel.sendText(chatId, `\u23F3 Saving context...`, "plain");
15595
+ await channel.sendText(chatId, `\u23F3 Saving context...`, { parseMode: "plain" });
15310
15596
  }
15311
15597
  const summarized = await summarizeWithFallbackChain(chatId, backendId);
15312
15598
  const bridge = buildContextBridge(chatId);
@@ -15314,9 +15600,9 @@ async function doBackendSwitch(chatId, backendId, channel) {
15314
15600
  setPendingContextBridge(chatId, bridge);
15315
15601
  }
15316
15602
  if (summarized) {
15317
- await channel.sendText(chatId, "\u{1F4BE} Context saved \u2014 session summarized to memory.", "plain");
15603
+ await channel.sendText(chatId, "\u{1F4BE} Context saved \u2014 session summarized to memory.", { parseMode: "plain" });
15318
15604
  } else if (bridge) {
15319
- await channel.sendText(chatId, "\u{1F4AC} Context preserved.", "plain");
15605
+ await channel.sendText(chatId, "\u{1F4AC} Context preserved.", { parseMode: "plain" });
15320
15606
  }
15321
15607
  clearSession(chatId);
15322
15608
  clearModel(chatId);
@@ -15324,7 +15610,7 @@ async function doBackendSwitch(chatId, backendId, channel) {
15324
15610
  clearChatGeminiSlot(chatId);
15325
15611
  setBackend(chatId, backendId);
15326
15612
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${targetAdapter.displayName}`, detail: { field: "backend", value: backendId } });
15327
- await channel.sendText(chatId, `\u2705 Switched to ${targetAdapter.displayName}. Ready!`, "plain");
15613
+ await channel.sendText(chatId, `\u2705 Switched to ${targetAdapter.displayName}. Ready!`, { parseMode: "plain" });
15328
15614
  }
15329
15615
  async function sendHistoryView(chatId, channel, filter) {
15330
15616
  const limit = filter.limit ?? 10;
@@ -15341,7 +15627,7 @@ async function sendHistoryView(chatId, channel, filter) {
15341
15627
  }
15342
15628
  const userMsgs = rows.filter((r) => r.role === "user").slice(0, limit);
15343
15629
  if (userMsgs.length === 0) {
15344
- await channel.sendText(chatId, "No conversation history found.", "plain");
15630
+ await channel.sendText(chatId, "No conversation history found.", { parseMode: "plain" });
15345
15631
  return;
15346
15632
  }
15347
15633
  const lines = [`\u{1F4CB} Recent Conversation (last ${userMsgs.length})`, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"];
@@ -15350,7 +15636,7 @@ async function sendHistoryView(chatId, channel, filter) {
15350
15636
  const preview = row.content.slice(0, 80) + (row.content.length > 80 ? "\u2026" : "");
15351
15637
  lines.push(`[${row.backend ?? "?"} \xB7 ${date}] ${preview}`);
15352
15638
  }
15353
- await channel.sendText(chatId, lines.join("\n"), "plain");
15639
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
15354
15640
  const filterRow = [
15355
15641
  { label: "Show More (25)", data: "hist:recent:25" },
15356
15642
  { label: "Today", data: "hist:today" },
@@ -15366,7 +15652,7 @@ async function sendHistoryView(chatId, channel, filter) {
15366
15652
  async function sendMemoryPage(chatId, channel, page) {
15367
15653
  const memories = listMemories();
15368
15654
  if (memories.length === 0) {
15369
- await channel.sendText(chatId, "No memories stored yet.", "plain");
15655
+ await channel.sendText(chatId, "No memories stored yet.", { parseMode: "plain" });
15370
15656
  return;
15371
15657
  }
15372
15658
  const pageSize = 10;
@@ -15442,7 +15728,7 @@ async function sendHeartbeatKeyboard(chatId, channel) {
15442
15728
  async function sendForgetPicker(chatId, channel, page) {
15443
15729
  const memories = listMemories();
15444
15730
  if (memories.length === 0) {
15445
- await channel.sendText(chatId, "No memories to forget.", "plain");
15731
+ await channel.sendText(chatId, "No memories to forget.", { parseMode: "plain" });
15446
15732
  return;
15447
15733
  }
15448
15734
  const { text, buttons } = buildPaginatedKeyboard({
@@ -15478,7 +15764,7 @@ function getJobStatusLabel(job) {
15478
15764
  async function sendJobsBoard(chatId, channel, page) {
15479
15765
  const jobs = listJobs();
15480
15766
  if (jobs.length === 0) {
15481
- await channel.sendText(chatId, "No scheduled jobs.\n\nCreate one: /schedule <description>", "plain");
15767
+ await channel.sendText(chatId, "No scheduled jobs.\n\nCreate one: /schedule <description>", { parseMode: "plain" });
15482
15768
  return;
15483
15769
  }
15484
15770
  if (typeof channel.sendKeyboard !== "function") {
@@ -15489,7 +15775,7 @@ async function sendJobsBoard(chatId, channel, page) {
15489
15775
  return `#${j.id} [${status}] ${schedule2}${tz}
15490
15776
  ${j.description}`;
15491
15777
  });
15492
- await channel.sendText(chatId, lines.join("\n\n"), "plain");
15778
+ await channel.sendText(chatId, lines.join("\n\n"), { parseMode: "plain" });
15493
15779
  return;
15494
15780
  }
15495
15781
  const headerLines = [];
@@ -15534,7 +15820,7 @@ async function sendJobDetail(chatId, jobId, channel) {
15534
15820
  [{ label: "Refresh List", data: "job:back" }]
15535
15821
  ]);
15536
15822
  } else {
15537
- await channel.sendText(chatId, "Job not found.", "plain");
15823
+ await channel.sendText(chatId, "Job not found.", { parseMode: "plain" });
15538
15824
  }
15539
15825
  return;
15540
15826
  }
@@ -15560,7 +15846,7 @@ async function sendJobDetail(chatId, jobId, channel) {
15560
15846
  ];
15561
15847
  const text = lines.join("\n");
15562
15848
  if (typeof channel.sendKeyboard !== "function") {
15563
- await channel.sendText(chatId, text, "plain");
15849
+ await channel.sendText(chatId, text, { parseMode: "plain" });
15564
15850
  return;
15565
15851
  }
15566
15852
  const actionRow1 = [
@@ -15590,7 +15876,7 @@ async function sendJobRunsView(chatId, jobId, channel, page) {
15590
15876
  [{ label: "\u2190 Back to Job", data: `job:view:${jobId}` }]
15591
15877
  ]);
15592
15878
  } else {
15593
- await channel.sendText(chatId, msg, "plain");
15879
+ await channel.sendText(chatId, msg, { parseMode: "plain" });
15594
15880
  }
15595
15881
  return;
15596
15882
  }
@@ -15601,7 +15887,7 @@ async function sendJobRunsView(chatId, jobId, channel, page) {
15601
15887
  Error: ${r.error.slice(0, 100)}` : "";
15602
15888
  return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${duration}${error3}`;
15603
15889
  });
15604
- await channel.sendText(chatId, lines.join("\n\n"), "plain");
15890
+ await channel.sendText(chatId, lines.join("\n\n"), { parseMode: "plain" });
15605
15891
  return;
15606
15892
  }
15607
15893
  const statusEmoji = {
@@ -15648,11 +15934,11 @@ async function sendJobPicker(chatId, channel, action) {
15648
15934
  const jobs = action === "resume" ? allJobs.filter((j) => j.active && !j.enabled) : action === "pause" ? allJobs.filter((j) => j.active && j.enabled) : allJobs.filter((j) => j.active);
15649
15935
  if (jobs.length === 0) {
15650
15936
  const msg = action === "resume" ? "No paused jobs." : action === "pause" ? "No active jobs to pause." : "No active jobs.";
15651
- await channel.sendText(chatId, msg, "plain");
15937
+ await channel.sendText(chatId, msg, { parseMode: "plain" });
15652
15938
  return;
15653
15939
  }
15654
15940
  if (typeof channel.sendKeyboard !== "function") {
15655
- await channel.sendText(chatId, `Usage: /${action} <job-id>`, "plain");
15941
+ await channel.sendText(chatId, `Usage: /${action} <job-id>`, { parseMode: "plain" });
15656
15942
  return;
15657
15943
  }
15658
15944
  const actionLabel = action.charAt(0).toUpperCase() + action.slice(1);
@@ -15723,7 +16009,7 @@ async function handleCallback(chatId, data, channel) {
15723
16009
  const previous = getBackend(chatId);
15724
16010
  if (chosen === previous) {
15725
16011
  const adapter = getAdapter(chosen);
15726
- await channel.sendText(chatId, `Already using ${adapter.displayName}.`, "plain");
16012
+ await channel.sendText(chatId, `Already using ${adapter.displayName}.`, { parseMode: "plain" });
15727
16013
  return;
15728
16014
  }
15729
16015
  await sendBackendSwitchConfirmation(chatId, chosen, channel);
@@ -15737,14 +16023,14 @@ async function handleCallback(chatId, data, channel) {
15737
16023
  if (!getAllBackendIds().includes(chosen)) return;
15738
16024
  await doBackendSwitch(chatId, chosen, channel);
15739
16025
  } else if (data.startsWith("backend_cancel:") || data === "backend_cancel") {
15740
- await channel.sendText(chatId, "Switch cancelled.", "plain");
16026
+ await channel.sendText(chatId, "Switch cancelled.", { parseMode: "plain" });
15741
16027
  } else if (data.startsWith("model:")) {
15742
16028
  const chosen = data.slice(6);
15743
16029
  let adapter;
15744
16030
  try {
15745
16031
  adapter = getAdapterForChat(chatId);
15746
16032
  } catch {
15747
- await channel.sendText(chatId, "No backend set. Use /backend first.", "plain");
16033
+ await channel.sendText(chatId, "No backend set. Use /backend first.", { parseMode: "plain" });
15748
16034
  return;
15749
16035
  }
15750
16036
  const modelInfo = adapter.availableModels[chosen];
@@ -15766,30 +16052,30 @@ Select thinking/effort level:`,
15766
16052
  thinkingButtons
15767
16053
  );
15768
16054
  } else {
15769
- await channel.sendText(chatId, `Model set to ${modelInfo.label}. Session continues.`, "plain");
16055
+ await channel.sendText(chatId, `Model set to ${modelInfo.label}. Session continues.`, { parseMode: "plain" });
15770
16056
  }
15771
16057
  } else {
15772
- await channel.sendText(chatId, `Model switched to ${modelInfo.label}. Session continues.`, "plain");
16058
+ await channel.sendText(chatId, `Model switched to ${modelInfo.label}. Session continues.`, { parseMode: "plain" });
15773
16059
  }
15774
16060
  } else if (data.startsWith("thinking:")) {
15775
16061
  const level = data.slice(9);
15776
16062
  setThinkingLevel(chatId, level);
15777
16063
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Thinking level set to ${level}`, detail: { field: "thinking", value: level } });
15778
16064
  const label2 = level === "auto" ? "Auto" : level.replace("_", " ").replace(/\b\w/g, (c) => c.toUpperCase());
15779
- await channel.sendText(chatId, `Thinking level set to: ${label2}`, "plain");
16065
+ await channel.sendText(chatId, `Thinking level set to: ${label2}`, { parseMode: "plain" });
15780
16066
  } else if (data.startsWith("summarizer:")) {
15781
16067
  const rest = data.slice(11);
15782
16068
  if (rest === "auto") {
15783
16069
  clearSummarizer(chatId);
15784
- await channel.sendText(chatId, "Summarizer set to auto (uses active backend).", "plain");
16070
+ await channel.sendText(chatId, "Summarizer set to auto (uses active backend).", { parseMode: "plain" });
15785
16071
  } else if (rest === "off") {
15786
16072
  setSummarizer(chatId, "off", null);
15787
- await channel.sendText(chatId, "Session summarization disabled.", "plain");
16073
+ await channel.sendText(chatId, "Session summarization disabled.", { parseMode: "plain" });
15788
16074
  } else {
15789
16075
  const [bk, ...modelParts] = rest.split(":");
15790
16076
  const mdl = modelParts.join(":") || null;
15791
16077
  setSummarizer(chatId, bk, mdl);
15792
- await channel.sendText(chatId, `Summarizer pinned to ${bk}:${mdl ?? "default"}.`, "plain");
16078
+ await channel.sendText(chatId, `Summarizer pinned to ${bk}:${mdl ?? "default"}.`, { parseMode: "plain" });
15793
16079
  }
15794
16080
  } else if (data.startsWith("perms:")) {
15795
16081
  let chosen = data.slice(6);
@@ -15797,7 +16083,7 @@ Select thinking/effort level:`,
15797
16083
  if (!PERM_MODES[chosen]) return;
15798
16084
  const previous = getMode(chatId);
15799
16085
  if (chosen === previous) {
15800
- await channel.sendText(chatId, `Already in ${chosen} mode.`, "plain");
16086
+ await channel.sendText(chatId, `Already in ${chosen} mode.`, { parseMode: "plain" });
15801
16087
  return;
15802
16088
  }
15803
16089
  setMode(chatId, chosen);
@@ -15806,14 +16092,14 @@ Select thinking/effort level:`,
15806
16092
  chatId,
15807
16093
  `Permission mode: ${chosen}
15808
16094
  ${PERM_MODES[chosen]}`,
15809
- "plain"
16095
+ { parseMode: "plain" }
15810
16096
  );
15811
16097
  } else if (data.startsWith("perm:escalate:")) {
15812
16098
  const targetMode = data.slice(14);
15813
16099
  if (!PERM_MODES[targetMode]) return;
15814
16100
  setMode(chatId, targetMode);
15815
16101
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Permission escalated to ${targetMode}`, detail: { field: "permissions", value: targetMode } });
15816
- await channel.sendText(chatId, `Switched to ${targetMode} mode. Re-processing your request...`, "plain");
16102
+ await channel.sendText(chatId, `Switched to ${targetMode} mode. Re-processing your request...`, { parseMode: "plain" });
15817
16103
  const pending = getPendingEscalation(chatId);
15818
16104
  if (pending) {
15819
16105
  removePendingEscalation(chatId);
@@ -15821,12 +16107,12 @@ ${PERM_MODES[chosen]}`,
15821
16107
  }
15822
16108
  } else if (data === "perm:deny") {
15823
16109
  removePendingEscalation(chatId);
15824
- await channel.sendText(chatId, "Keeping current mode.", "plain");
16110
+ await channel.sendText(chatId, "Keeping current mode.", { parseMode: "plain" });
15825
16111
  } else if (data.startsWith("verbose:")) {
15826
16112
  const chosen = data.slice(8);
15827
16113
  if (!VERBOSE_LEVELS[chosen]) return;
15828
16114
  setVerboseLevel(chatId, chosen);
15829
- await channel.sendText(chatId, `Tool visibility: ${VERBOSE_LEVELS[chosen]}`, "plain");
16115
+ await channel.sendText(chatId, `Tool visibility: ${VERBOSE_LEVELS[chosen]}`, { parseMode: "plain" });
15830
16116
  } else if (data.startsWith("tool:toggle:")) {
15831
16117
  const toolName = data.slice(12);
15832
16118
  if (!ALL_TOOLS.includes(toolName)) return;
@@ -15834,11 +16120,11 @@ ${PERM_MODES[chosen]}`,
15834
16120
  await channel.sendText(
15835
16121
  chatId,
15836
16122
  `${newState ? "\u2705" : "\u274C"} ${toolName} ${newState ? "enabled" : "disabled"}`,
15837
- "plain"
16123
+ { parseMode: "plain" }
15838
16124
  );
15839
16125
  } else if (data === "tool:reset") {
15840
16126
  resetTools(chatId);
15841
- await channel.sendText(chatId, "Tool configuration reset \u2014 all tools enabled.", "plain");
16127
+ await channel.sendText(chatId, "Tool configuration reset \u2014 all tools enabled.", { parseMode: "plain" });
15842
16128
  } else if (data.startsWith("sched:")) {
15843
16129
  await handleWizardCallback(chatId, data, channel);
15844
16130
  } else if (data.startsWith("profile:")) {
@@ -15847,9 +16133,9 @@ ${PERM_MODES[chosen]}`,
15847
16133
  const parts = data.slice(11).split(":");
15848
16134
  if (parts[0] === "yes" && parts.length >= 3) {
15849
16135
  appendToUserProfile(parts[1], parts.slice(2).join(":"));
15850
- await channel.sendText(chatId, `Preference saved: ${parts[1]}`, "plain");
16136
+ await channel.sendText(chatId, `Preference saved: ${parts[1]}`, { parseMode: "plain" });
15851
16137
  } else {
15852
- await channel.sendText(chatId, "Preference not saved.", "plain");
16138
+ await channel.sendText(chatId, "Preference not saved.", { parseMode: "plain" });
15853
16139
  }
15854
16140
  } else if (data.startsWith("shell:")) {
15855
16141
  const parts = data.split(":");
@@ -15858,7 +16144,7 @@ ${PERM_MODES[chosen]}`,
15858
16144
  if (action === "confirm") {
15859
16145
  const pending = getPendingCommand(id);
15860
16146
  if (!pending) {
15861
- await channel.sendText(chatId, "Confirmation expired. Please re-send the command.", "plain");
16147
+ await channel.sendText(chatId, "Confirmation expired. Please re-send the command.", { parseMode: "plain" });
15862
16148
  return;
15863
16149
  }
15864
16150
  removePendingCommand(id);
@@ -15869,27 +16155,27 @@ ${PERM_MODES[chosen]}`,
15869
16155
  }
15870
16156
  } else if (action === "cancel") {
15871
16157
  removePendingCommand(id);
15872
- await channel.sendText(chatId, "Command cancelled.", "plain");
16158
+ await channel.sendText(chatId, "Command cancelled.", { parseMode: "plain" });
15873
16159
  }
15874
16160
  } else if (data.startsWith("cwd:")) {
15875
16161
  const parts = data.split(":");
15876
16162
  const action = parts[1];
15877
16163
  const targetChatId = parts.slice(2).join(":");
15878
16164
  if (action === "keep") {
15879
- await channel.sendText(chatId, "Session kept. The agent will continue with existing context.", "plain");
16165
+ await channel.sendText(chatId, "Session kept. The agent will continue with existing context.", { parseMode: "plain" });
15880
16166
  } else if (action === "summarize") {
15881
16167
  await summarizeSession(targetChatId);
15882
16168
  clearSession(targetChatId);
15883
- await channel.sendText(chatId, "Session summarized and reset. Context preserved in memory.", "plain");
16169
+ await channel.sendText(chatId, "Session summarized and reset. Context preserved in memory.", { parseMode: "plain" });
15884
16170
  } else if (action === "reset") {
15885
16171
  clearSession(targetChatId);
15886
- await channel.sendText(chatId, "Session reset. Clean slate.", "plain");
16172
+ await channel.sendText(chatId, "Session reset. Clean slate.", { parseMode: "plain" });
15887
16173
  }
15888
16174
  } else if (data.startsWith("cwdpick:")) {
15889
16175
  const alias = data.slice(8);
15890
16176
  const bookmark = getBookmark(chatId, alias);
15891
16177
  if (!bookmark) {
15892
- await channel.sendText(chatId, `Bookmark '${alias}' no longer exists.`, "plain");
16178
+ await channel.sendText(chatId, `Bookmark '${alias}' no longer exists.`, { parseMode: "plain" });
15893
16179
  return;
15894
16180
  }
15895
16181
  setCwd(chatId, bookmark.path);
@@ -15904,22 +16190,36 @@ ${PERM_MODES[chosen]}`,
15904
16190
  if (action === "now" && pending) {
15905
16191
  pendingInterrupts.delete(targetChatId);
15906
16192
  stopAgent(targetChatId);
15907
- await channel.sendText(chatId, "\u26A1 Stopping current task and processing your message\u2026", "plain");
16193
+ await channel.sendText(chatId, "\u26A1 Stopping current task and processing your message\u2026", { parseMode: "plain" });
15908
16194
  bypassBusyCheck.add(targetChatId);
15909
16195
  handleMessage(pending.msg, pending.channel).catch(() => {
15910
16196
  });
15911
16197
  } else if (action === "queue" && pending) {
15912
16198
  pendingInterrupts.delete(targetChatId);
15913
16199
  bypassBusyCheck.add(targetChatId);
15914
- await channel.sendText(chatId, "\u{1F4E5} Message queued \u2014 will process after current task.", "plain");
16200
+ await channel.sendText(chatId, "\u{1F4E5} Message queued \u2014 will process after current task.", { parseMode: "plain" });
15915
16201
  handleMessage(pending.msg, pending.channel).catch(() => {
15916
16202
  });
16203
+ } else if (action === "sidequest") {
16204
+ const pending2 = pendingInterrupts.get(targetChatId);
16205
+ if (pending2) {
16206
+ pendingInterrupts.delete(targetChatId);
16207
+ handleSideQuest(targetChatId, pending2.msg, pending2.channel).catch(
16208
+ (err) => error(`[router] Side quest error for ${targetChatId}:`, err)
16209
+ );
16210
+ } else {
16211
+ await channel.sendText(chatId, "Main task finished \u2014 your message was already processed.", { parseMode: "plain" });
16212
+ }
15917
16213
  } else if (action === "discard") {
15918
16214
  pendingInterrupts.delete(targetChatId);
15919
- await channel.sendText(chatId, "\u{1F5D1} Message discarded.", "plain");
16215
+ await channel.sendText(chatId, "\u{1F5D1} Message discarded.", { parseMode: "plain" });
15920
16216
  } else {
15921
- await channel.sendText(chatId, "Message already processed or expired.", "plain");
16217
+ await channel.sendText(chatId, "Message already processed or expired.", { parseMode: "plain" });
15922
16218
  }
16219
+ } else if (data.startsWith("sq:cancel:")) {
16220
+ const sqId = data.slice("sq:cancel:".length);
16221
+ stopAgent(sqId);
16222
+ await channel.sendText(chatId, "\u{1F5FA} Side quest cancelled.", { parseMode: "plain" });
15923
16223
  } else if (data.startsWith("fallback:")) {
15924
16224
  const parts = data.split(":");
15925
16225
  const targetBackend = parts[1];
@@ -15927,7 +16227,7 @@ ${PERM_MODES[chosen]}`,
15927
16227
  const pendingMsg = pendingFallbackMessages.get(targetChatId);
15928
16228
  if (targetBackend === "wait") {
15929
16229
  pendingFallbackMessages.delete(targetChatId);
15930
- await channel.sendText(chatId, "OK \u2014 you can switch manually with /backend when ready.", "plain");
16230
+ await channel.sendText(chatId, "OK \u2014 you can switch manually with /backend when ready.", { parseMode: "plain" });
15931
16231
  } else if (pendingMsg) {
15932
16232
  pendingFallbackMessages.delete(targetChatId);
15933
16233
  if (pendingMsg.agentMode) pendingMsg.msg.agentMode = pendingMsg.agentMode;
@@ -15937,10 +16237,10 @@ ${PERM_MODES[chosen]}`,
15937
16237
  clearSession(targetChatId);
15938
16238
  setBackend(targetChatId, targetBackend);
15939
16239
  const adapter = getAdapter(targetBackend);
15940
- await channel.sendText(chatId, `Switched to ${adapter.displayName}. Resending your message\u2026`, "plain");
16240
+ await channel.sendText(chatId, `Switched to ${adapter.displayName}. Resending your message\u2026`, { parseMode: "plain" });
15941
16241
  await handleMessage(pendingMsg.msg, pendingMsg.channel);
15942
16242
  } else {
15943
- await channel.sendText(chatId, "Fallback expired. Use /backend to switch manually.", "plain");
16243
+ await channel.sendText(chatId, "Fallback expired. Use /backend to switch manually.", { parseMode: "plain" });
15944
16244
  }
15945
16245
  } else if (data.startsWith("voice:")) {
15946
16246
  const action = data.slice(6);
@@ -15948,7 +16248,7 @@ ${PERM_MODES[chosen]}`,
15948
16248
  const current = isVoiceEnabled(chatId);
15949
16249
  const desired = action === "on";
15950
16250
  if (current !== desired) toggleVoice(chatId);
15951
- await channel.sendText(chatId, desired ? "\u{1F50A} Voice responses enabled." : "\u{1F507} Voice responses disabled.", "plain");
16251
+ await channel.sendText(chatId, desired ? "\u{1F50A} Voice responses enabled." : "\u{1F507} Voice responses disabled.", { parseMode: "plain" });
15952
16252
  }
15953
16253
  } else if (data.startsWith("style:")) {
15954
16254
  const selectedStyle = data.split(":")[1];
@@ -15963,7 +16263,7 @@ ${PERM_MODES[chosen]}`,
15963
16263
  ]
15964
16264
  ]);
15965
16265
  }
15966
- await channel.sendText(chatId, `Response style set to: ${selectedStyle}`, "plain");
16266
+ await channel.sendText(chatId, `Response style set to: ${selectedStyle}`, { parseMode: "plain" });
15967
16267
  }
15968
16268
  } else if (data.startsWith("agentmode:")) {
15969
16269
  const mode = data.split(":")[1];
@@ -15974,7 +16274,7 @@ ${PERM_MODES[chosen]}`,
15974
16274
  } catch {
15975
16275
  }
15976
16276
  clearSession(chatId);
15977
- await channel.sendText(chatId, `Agent mode set to <b>${mode}</b>. Session cleared.`, "html");
16277
+ await channel.sendText(chatId, `Agent mode set to <b>${mode}</b>. Session cleared.`, { parseMode: "html" });
15978
16278
  }
15979
16279
  return;
15980
16280
  } else if (data.startsWith("agents:")) {
@@ -15985,11 +16285,11 @@ ${PERM_MODES[chosen]}`,
15985
16285
  const agents2 = listActiveAgents(db3);
15986
16286
  const match = agents2.find((a) => a.id.startsWith(shortId));
15987
16287
  if (!match) {
15988
- await channel.sendText(chatId, `Agent ${shortId} not found or already stopped. Use /agents to refresh.`, "plain");
16288
+ await channel.sendText(chatId, `Agent ${shortId} not found or already stopped. Use /agents to refresh.`, { parseMode: "plain" });
15989
16289
  return;
15990
16290
  }
15991
16291
  const ok = cancelAgent(match.id);
15992
- await channel.sendText(chatId, ok ? `Agent ${match.id.slice(0, 8)} cancelled.` : "Could not cancel agent.", "plain");
16292
+ await channel.sendText(chatId, ok ? `Agent ${match.id.slice(0, 8)} cancelled.` : "Could not cancel agent.", { parseMode: "plain" });
15993
16293
  } else if (rest === "tasks") {
15994
16294
  const synth = { chatId, messageId: "", text: "", senderName: "User", type: "command", source: "telegram", command: "tasks", commandArgs: "" };
15995
16295
  await handleCommand(synth, channel);
@@ -16003,16 +16303,16 @@ ${PERM_MODES[chosen]}`,
16003
16303
  const agents2 = listActiveAgents(db3);
16004
16304
  const match = agents2.find((a) => a.id.startsWith(shortId));
16005
16305
  if (!match) {
16006
- await channel.sendText(chatId, `Agent ${shortId} not found or already stopped.`, "plain");
16306
+ await channel.sendText(chatId, `Agent ${shortId} not found or already stopped.`, { parseMode: "plain" });
16007
16307
  return;
16008
16308
  }
16009
16309
  const ok = cancelAgent(match.id);
16010
- await channel.sendText(chatId, ok ? `Agent ${match.id.slice(0, 8)} cancelled.` : "Could not cancel agent.", "plain");
16310
+ await channel.sendText(chatId, ok ? `Agent ${match.id.slice(0, 8)} cancelled.` : "Could not cancel agent.", { parseMode: "plain" });
16011
16311
  } else if (rest === "all") {
16012
16312
  const count = cancelAllAgents(chatId);
16013
- await channel.sendText(chatId, count > 0 ? `Cancelled ${count} agent(s).` : "No active agents to cancel.", "plain");
16313
+ await channel.sendText(chatId, count > 0 ? `Cancelled ${count} agent(s).` : "No active agents to cancel.", { parseMode: "plain" });
16014
16314
  } else if (rest === "cancel") {
16015
- await channel.sendText(chatId, "Cancelled.", "plain");
16315
+ await channel.sendText(chatId, "Cancelled.", { parseMode: "plain" });
16016
16316
  }
16017
16317
  return;
16018
16318
  } else if (data.startsWith("tasks:")) {
@@ -16022,7 +16322,7 @@ ${PERM_MODES[chosen]}`,
16022
16322
  const db3 = getDb();
16023
16323
  const task = getTask(db3, taskId);
16024
16324
  if (!task) {
16025
- await channel.sendText(chatId, "Task not found or outdated. Use /tasks to refresh.", "plain");
16325
+ await channel.sendText(chatId, "Task not found or outdated. Use /tasks to refresh.", { parseMode: "plain" });
16026
16326
  return;
16027
16327
  }
16028
16328
  const STATUS_EMOJI_TASK = {
@@ -16046,7 +16346,7 @@ ${PERM_MODES[chosen]}`,
16046
16346
  task.result ? `
16047
16347
  Result: ${task.result.slice(0, 500)}` : ""
16048
16348
  ].filter(Boolean);
16049
- await channel.sendText(chatId, lines.join("\n"), "plain");
16349
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
16050
16350
  }
16051
16351
  return;
16052
16352
  } else if (data.startsWith("grotation:")) {
@@ -16056,25 +16356,25 @@ Result: ${task.result.slice(0, 500)}` : ""
16056
16356
  if (mode === "accounts") {
16057
16357
  const oauthSlots = getGeminiSlots().filter((s) => s.enabled && s.slotType === "oauth");
16058
16358
  if (oauthSlots.length === 0) {
16059
- await channel.sendText(chatId, "\u26A0\uFE0F No OAuth accounts configured. Add one with <code>cc-claw gemini add-account</code> or choose a different mode.", "html");
16359
+ await channel.sendText(chatId, "\u26A0\uFE0F No OAuth accounts configured. Add one with <code>cc-claw gemini add-account</code> or choose a different mode.", { parseMode: "html" });
16060
16360
  return;
16061
16361
  }
16062
16362
  } else if (mode === "keys") {
16063
16363
  const keySlots = getGeminiSlots().filter((s) => s.enabled && s.slotType === "api_key");
16064
16364
  if (keySlots.length === 0) {
16065
- await channel.sendText(chatId, "\u26A0\uFE0F No API keys configured. Add one with <code>cc-claw gemini add-key</code> or choose a different mode.", "html");
16365
+ await channel.sendText(chatId, "\u26A0\uFE0F No API keys configured. Add one with <code>cc-claw gemini add-key</code> or choose a different mode.", { parseMode: "html" });
16066
16366
  return;
16067
16367
  }
16068
16368
  }
16069
16369
  setGeminiRotationMode(mode);
16070
16370
  const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Accounts only", keys: "\u{1F511} Keys only" };
16071
- await channel.sendText(chatId, `Rotation mode set to <b>${modeLabels[mode]}</b>.`, "html");
16371
+ await channel.sendText(chatId, `Rotation mode set to <b>${modeLabels[mode]}</b>.`, { parseMode: "html" });
16072
16372
  return;
16073
16373
  } else if (data.startsWith("gslot:")) {
16074
16374
  const val = data.split(":")[1];
16075
16375
  if (val === "auto") {
16076
16376
  clearChatGeminiSlot(chatId);
16077
- await channel.sendText(chatId, "Gemini slot set to <b>\u{1F504} auto rotation</b>.", "html");
16377
+ await channel.sendText(chatId, "Gemini slot set to <b>\u{1F504} auto rotation</b>.", { parseMode: "html" });
16078
16378
  } else {
16079
16379
  const slotId = parseInt(val, 10);
16080
16380
  const slots = getGeminiSlots();
@@ -16083,14 +16383,14 @@ Result: ${task.result.slice(0, 500)}` : ""
16083
16383
  pinChatGeminiSlot(chatId, slotId);
16084
16384
  const label2 = slot.label || `slot-${slot.id}`;
16085
16385
  const icon = slot.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
16086
- await channel.sendText(chatId, `Pinned to ${icon} <b>${label2}</b>`, "html");
16386
+ await channel.sendText(chatId, `Pinned to ${icon} <b>${label2}</b>`, { parseMode: "html" });
16087
16387
  }
16088
16388
  }
16089
16389
  return;
16090
16390
  } else if (data === "gopen:accounts") {
16091
16391
  const slots = getGeminiSlots();
16092
16392
  if (slots.length === 0) {
16093
- await channel.sendText(chatId, "No Gemini credentials configured.\nAdd with: <code>cc-claw gemini add-key</code> or <code>cc-claw gemini add-account</code>", "html");
16393
+ await channel.sendText(chatId, "No Gemini credentials configured.\nAdd with: <code>cc-claw gemini add-key</code> or <code>cc-claw gemini add-account</code>", { parseMode: "html" });
16094
16394
  return;
16095
16395
  }
16096
16396
  if (typeof channel.sendKeyboard === "function") {
@@ -16122,7 +16422,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16122
16422
  await channel.sendText(
16123
16423
  chatId,
16124
16424
  value === "on" ? "\u{1F9E0} Model signature enabled. Each response will show the active model and thinking level." : "Model signature disabled.",
16125
- "plain"
16425
+ { parseMode: "plain" }
16126
16426
  );
16127
16427
  } else if (data.startsWith("vcfg:")) {
16128
16428
  const parts = data.slice(5).split(":");
@@ -16134,14 +16434,14 @@ Result: ${task.result.slice(0, 500)}` : ""
16134
16434
  await channel.sendText(
16135
16435
  chatId,
16136
16436
  "\u26A0\uFE0F Grok requires `XAI_API_KEY` to be set.\n\nAdd it to your config:\n```\necho 'XAI_API_KEY=your-key-here' >> ~/.cc-claw/.env\ncc-claw service restart\n```\n\nGet a key at: https://console.x.ai/team/default/api-keys\n\nSetting Grok as your provider \u2014 it will activate once the key is added.",
16137
- "markdown"
16437
+ { parseMode: "markdown" }
16138
16438
  );
16139
16439
  }
16140
16440
  if (provider === "elevenlabs" && !process.env.ELEVENLABS_API_KEY) {
16141
16441
  await channel.sendText(
16142
16442
  chatId,
16143
16443
  "\u26A0\uFE0F ElevenLabs requires `ELEVENLABS_API_KEY` to be set.\n\nAdd it to your config:\n```\necho 'ELEVENLABS_API_KEY=your-key-here' >> ~/.cc-claw/.env\ncc-claw service restart\n```\n\nGet a key at: https://elevenlabs.io/api\n\nSetting ElevenLabs as your provider \u2014 it will activate once the key is added.",
16144
- "markdown"
16444
+ { parseMode: "markdown" }
16145
16445
  );
16146
16446
  }
16147
16447
  const defaultVoice = provider === "grok" ? "eve" : provider === "macos" ? "Samantha" : "21m00Tcm4TlvDq8ikWAM";
@@ -16153,7 +16453,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16153
16453
  const config2 = getVoiceConfig(chatId);
16154
16454
  setVoiceProvider(chatId, config2.provider, voiceId);
16155
16455
  const voiceName = config2.provider === "elevenlabs" ? ELEVENLABS_VOICES[voiceId]?.name ?? voiceId : config2.provider === "macos" ? MACOS_VOICES[voiceId]?.name ?? voiceId : voiceId;
16156
- await channel.sendText(chatId, `\u2705 Voice set to: ${voiceName}`, "plain");
16456
+ await channel.sendText(chatId, `\u2705 Voice set to: ${voiceName}`, { parseMode: "plain" });
16157
16457
  }
16158
16458
  } else if (data.startsWith("skills:page:")) {
16159
16459
  const page = parseInt(data.slice(12), 10);
@@ -16172,7 +16472,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16172
16472
  const skills2 = await discoverAllSkills();
16173
16473
  const skill = skillSource ? skills2.find((s) => s.name === skillName && s.source === skillSource) : skills2.find((s) => s.name === skillName);
16174
16474
  if (!skill) {
16175
- await channel.sendText(chatId, `Skill "${skillName}" not found.`, "plain");
16475
+ await channel.sendText(chatId, `Skill "${skillName}" not found.`, { parseMode: "plain" });
16176
16476
  return;
16177
16477
  }
16178
16478
  const activeBackend = getBackend(chatId) ?? "claude";
@@ -16180,13 +16480,13 @@ Result: ${task.result.slice(0, 500)}` : ""
16180
16480
  await channel.sendText(
16181
16481
  chatId,
16182
16482
  `Note: "${skillName}" lists compatible backends as [${skill.compatibleBackends.join(", ")}], but active backend is ${activeBackend}. Proceeding anyway.`,
16183
- "plain"
16483
+ { parseMode: "plain" }
16184
16484
  );
16185
16485
  }
16186
16486
  const raw = await readFile5(skill.filePath, "utf-8");
16187
16487
  const skillContent = stripFrontmatter2(raw);
16188
16488
  const tags = skill.sources.join(", ");
16189
- await channel.sendText(chatId, `Loading skill: ${skillName} [${tags}]...`, "plain");
16489
+ await channel.sendText(chatId, `Loading skill: ${skillName} [${tags}]...`, { parseMode: "plain" });
16190
16490
  const skillModel = resolveModel(chatId);
16191
16491
  const sMode = getMode(chatId);
16192
16492
  const sVerbose = getVerboseLevel(chatId);
@@ -16199,19 +16499,65 @@ Result: ${task.result.slice(0, 500)}` : ""
16199
16499
  const action = parts[1];
16200
16500
  const idStr = parts[2];
16201
16501
  switch (action) {
16502
+ case "menu": {
16503
+ const { getReflectionStatus: getRefStatus, getUnprocessedSignalCount: getUnprocessedSignalCount2, getPendingInsightCount: getPendingInsightCount2, getLastAnalysisTime: getLastAnalysisTime2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16504
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16505
+ const menuActive = getRefStatus(getDb2(), chatId) === "active";
16506
+ if (!menuActive) {
16507
+ const { buildEvolveOnboardingKeyboard: buildEvolveOnboardingKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16508
+ const text = [
16509
+ "Self-Learning & Evolution",
16510
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
16511
+ "",
16512
+ "Teach your assistant to improve over time.",
16513
+ "",
16514
+ "When enabled, CC-Claw watches for corrections,",
16515
+ "preferences, and frustration in your messages,",
16516
+ "then proposes changes to its personality and",
16517
+ "behavior files (SOUL.md, USER.md).",
16518
+ "",
16519
+ "You review and approve every change."
16520
+ ].join("\n");
16521
+ await channel.sendKeyboard(chatId, text, buildEvolveOnboardingKeyboard2());
16522
+ } else {
16523
+ const { buildEvolveMenuKeyboard: buildEvolveMenuKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16524
+ const signals = getUnprocessedSignalCount2(getDb2(), chatId);
16525
+ const pending = getPendingInsightCount2(getDb2(), chatId);
16526
+ const lastTime = getLastAnalysisTime2(getDb2(), chatId);
16527
+ let lastText = "never";
16528
+ if (lastTime) {
16529
+ const diffMs = Date.now() - (/* @__PURE__ */ new Date(lastTime + "Z")).getTime();
16530
+ const diffHours = Math.floor(diffMs / 36e5);
16531
+ if (diffHours < 1) lastText = "< 1 hour ago";
16532
+ else if (diffHours < 24) lastText = `${diffHours}h ago`;
16533
+ else lastText = `${Math.floor(diffHours / 24)}d ago`;
16534
+ }
16535
+ const lines = [
16536
+ "Self-Learning & Evolution",
16537
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
16538
+ "",
16539
+ `\u2705 Active`,
16540
+ `Signals: ${signals} pending \xB7 Proposals: ${pending}`,
16541
+ `Last analysis: ${lastText}`
16542
+ ];
16543
+ await channel.sendKeyboard(chatId, lines.join("\n"), buildEvolveMenuKeyboard2({ pendingProposals: pending, unprocessedSignals: signals }));
16544
+ }
16545
+ break;
16546
+ }
16202
16547
  case "analyze": {
16203
16548
  const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
16204
16549
  const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16205
- await channel.sendText(chatId, "Analyzing recent interactions...", "plain");
16550
+ await channel.sendText(chatId, "Analyzing recent interactions...", { parseMode: "plain" });
16206
16551
  try {
16207
- const insights = await runAnalysis2(chatId);
16552
+ const insights = await runAnalysis2(chatId, { force: true });
16208
16553
  if (insights.length === 0) {
16209
- await channel.sendText(chatId, "No new insights from recent interactions.", "plain");
16554
+ await channel.sendText(chatId, "No new insights from recent interactions.", { parseMode: "plain" });
16210
16555
  } else {
16211
- await channel.sendText(chatId, formatNightlySummary2(insights), "plain");
16556
+ const nightlyItems = insights.map((ins, i) => ({ id: i + 1, ...ins }));
16557
+ await channel.sendText(chatId, formatNightlySummary2(nightlyItems), { parseMode: "plain" });
16212
16558
  }
16213
16559
  } catch (e) {
16214
- await channel.sendText(chatId, `Analysis failed: ${e}`, "plain");
16560
+ await channel.sendText(chatId, `Analysis failed: ${e}`, { parseMode: "plain" });
16215
16561
  }
16216
16562
  break;
16217
16563
  }
@@ -16221,7 +16567,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16221
16567
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16222
16568
  const pending = getPendingInsights2(getDb2(), chatId);
16223
16569
  if (pending.length === 0) {
16224
- await channel.sendText(chatId, "No pending proposals.", "plain");
16570
+ await channel.sendText(chatId, "No pending proposals.", { parseMode: "plain" });
16225
16571
  } else {
16226
16572
  for (const insight of pending.slice(0, 5)) {
16227
16573
  const card = formatProposalCard2(insight);
@@ -16229,7 +16575,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16229
16575
  await channel.sendKeyboard(chatId, card, kb);
16230
16576
  }
16231
16577
  if (pending.length > 5) {
16232
- await channel.sendText(chatId, `${pending.length - 5} more proposals. Run /evolve again to see next batch.`, "plain");
16578
+ await channel.sendText(chatId, `${pending.length - 5} more proposals. Run /evolve again to see next batch.`, { parseMode: "plain" });
16233
16579
  }
16234
16580
  }
16235
16581
  break;
@@ -16240,25 +16586,48 @@ Result: ${task.result.slice(0, 500)}` : ""
16240
16586
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16241
16587
  const insight = getInsightById2(getDb2(), parseInt(idStr, 10));
16242
16588
  if (insight?.proposedDiff) {
16243
- await channel.sendText(chatId, formatDiffCodeBlock2(insight.proposedDiff), "plain");
16589
+ await channel.sendText(chatId, formatDiffCodeBlock2(insight.proposedDiff), { parseMode: "plain" });
16244
16590
  }
16245
16591
  break;
16246
16592
  }
16247
16593
  case "apply": {
16248
16594
  const { applyInsight: applyInsight2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
16249
16595
  const result = await applyInsight2(parseInt(idStr, 10));
16250
- await channel.sendText(chatId, result.message, "plain");
16596
+ await channel.sendText(chatId, result.message, { parseMode: "plain" });
16251
16597
  break;
16252
16598
  }
16253
16599
  case "skip": {
16254
- await channel.sendText(chatId, "Skipped \u2014 will show again next review.", "plain");
16600
+ await channel.sendText(chatId, "Skipped \u2014 will show again next review.", { parseMode: "plain" });
16601
+ break;
16602
+ }
16603
+ case "discuss": {
16604
+ const insId = parseInt(idStr, 10);
16605
+ activeProposalDiscussion.set(chatId, insId);
16606
+ await channel.sendKeyboard(chatId, `Discussing proposal #${insId}. Send your questions \u2014 type "done" when finished.`, [
16607
+ [{ label: "Done", data: `evolve:discuss-done:${insId}` }]
16608
+ ]);
16609
+ break;
16610
+ }
16611
+ case "discuss-done": {
16612
+ activeProposalDiscussion.delete(chatId);
16613
+ const { getInsightById: getIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16614
+ const { formatProposalCard: fmtCard, buildProposalKeyboard: buildKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16615
+ const { getDb: getDatabase } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16616
+ const ins = getIns(getDatabase(), parseInt(idStr, 10));
16617
+ if (ins && ins.status === "pending") {
16618
+ const card = fmtCard(ins);
16619
+ const kb = buildKb(ins.id, ins.category);
16620
+ await channel.sendKeyboard(chatId, "Updated proposal:\n\n" + card, kb);
16621
+ } else {
16622
+ await channel.sendText(chatId, "Discussion ended.", { parseMode: "plain" });
16623
+ }
16255
16624
  break;
16256
16625
  }
16257
16626
  case "reject": {
16258
16627
  const { updateInsightStatus: updateInsightStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16259
16628
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16260
16629
  updateInsightStatus2(getDb2(), parseInt(idStr, 10), "rejected");
16261
- await channel.sendText(chatId, "Rejected. Won't propose similar changes.", "plain");
16630
+ await channel.sendText(chatId, "Rejected. Won't propose similar changes.", { parseMode: "plain" });
16262
16631
  break;
16263
16632
  }
16264
16633
  case "stats": {
@@ -16268,12 +16637,23 @@ Result: ${task.result.slice(0, 500)}` : ""
16268
16637
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16269
16638
  const reportData = buildGrowthReportData2(getDb2(), chatId, 30);
16270
16639
  const modelData = buildModelPerformanceData2(getDb2(), chatId, 30);
16271
- let report = formatGrowthReport2(reportData, modelData);
16640
+ const metricsForReport = {
16641
+ correctionsBefore: reportData.avgCorrectionsFirstHalf,
16642
+ correctionsAfter: reportData.avgCorrectionsSecondHalf,
16643
+ praiseRatio: reportData.praiseRatio,
16644
+ insightsApplied: reportData.totalInsightsApplied,
16645
+ pendingCount: reportData.pendingCount,
16646
+ topInsight: reportData.topInsightId != null ? {
16647
+ insight: `#${reportData.topInsightId}`,
16648
+ effectiveness: reportData.topInsightEffectiveness ?? 0
16649
+ } : null
16650
+ };
16651
+ let report = formatGrowthReport2(metricsForReport, modelData);
16272
16652
  const drift = calculateDrift2(chatId);
16273
16653
  if (drift && (drift.soulDrift > 0.5 || drift.userDrift > 0.5)) {
16274
16654
  report += "\n\nSOUL.md has changed significantly since reflection started.\nTap History in /evolve to review all applied changes.";
16275
16655
  }
16276
- await channel.sendText(chatId, report, "plain");
16656
+ await channel.sendText(chatId, report, { parseMode: "plain" });
16277
16657
  break;
16278
16658
  }
16279
16659
  case "history": {
@@ -16293,7 +16673,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16293
16673
  msg += `#${i.id} [${i.category}] ${i.insight}
16294
16674
  `;
16295
16675
  });
16296
- await channel.sendText(chatId, msg, "plain");
16676
+ await channel.sendText(chatId, msg, { parseMode: "plain" });
16297
16677
  break;
16298
16678
  }
16299
16679
  case "toggle": {
@@ -16311,12 +16691,12 @@ Result: ${task.result.slice(0, 500)}` : ""
16311
16691
  setReflectionStatus2(getDb2(), chatId, "active", soul, user);
16312
16692
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
16313
16693
  logActivity2(getDb2(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
16314
- await channel.sendText(chatId, "Self-learning enabled. Signal detection is now active.\nCreate a nightly cron job with /schedule to enable automatic analysis.", "plain");
16694
+ await channel.sendText(chatId, "\u2705 Self-learning enabled. Signal detection is now active.\nCreate a nightly cron job with /schedule to enable automatic analysis.", { parseMode: "plain" });
16315
16695
  } else {
16316
- setReflectionStatus2(getDb2(), chatId, "frozen", null, null);
16696
+ setReflectionStatus2(getDb2(), chatId, "frozen", void 0, void 0);
16317
16697
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
16318
16698
  logActivity2(getDb2(), { chatId, source: "telegram", eventType: "reflection_frozen", summary: "Reflection disabled" });
16319
- await channel.sendText(chatId, "Self-learning disabled. No signals will be collected.", "plain");
16699
+ await channel.sendText(chatId, "\u26D4 Self-learning disabled. No signals will be collected.", { parseMode: "plain" });
16320
16700
  }
16321
16701
  break;
16322
16702
  }
@@ -16324,14 +16704,14 @@ Result: ${task.result.slice(0, 500)}` : ""
16324
16704
  if (idStr) {
16325
16705
  const { rollbackInsight: rollbackInsight2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
16326
16706
  const result = await rollbackInsight2(parseInt(idStr, 10));
16327
- await channel.sendText(chatId, result.message, "plain");
16707
+ await channel.sendText(chatId, result.message, { parseMode: "plain" });
16328
16708
  } else {
16329
16709
  const { getAppliedInsights: getAppliedInsights2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16330
16710
  const { buildUndoKeyboard: buildUndoKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16331
16711
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16332
16712
  const applied = getAppliedInsights2(getDb2(), chatId, 10);
16333
16713
  if (applied.length === 0) {
16334
- await channel.sendText(chatId, "No applied insights to undo.", "plain");
16714
+ await channel.sendText(chatId, "No applied insights to undo.", { parseMode: "plain" });
16335
16715
  } else {
16336
16716
  const kb = buildUndoKeyboard2(applied);
16337
16717
  await channel.sendKeyboard(chatId, "Select an insight to undo:", kb);
@@ -16344,7 +16724,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16344
16724
  const { setReflectionModelConfig: setReflectionModelConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16345
16725
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16346
16726
  setReflectionModelConfig2(getDb2(), chatId, idStr);
16347
- await channel.sendText(chatId, `Analysis model set to: ${idStr}`, "plain");
16727
+ await channel.sendText(chatId, `Analysis model set to: ${idStr}`, { parseMode: "plain" });
16348
16728
  } else {
16349
16729
  const { getReflectionModelConfig: getReflectionModelConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16350
16730
  const { buildModelKeyboard: buildModelKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
@@ -16362,16 +16742,16 @@ Result: ${task.result.slice(0, 500)}` : ""
16362
16742
  if (action === "all") {
16363
16743
  const pendingIds = getLoggedChatIds();
16364
16744
  if (pendingIds.length === 0) {
16365
- await channel.sendText(chatId, "No pending sessions to summarize.", "plain");
16745
+ await channel.sendText(chatId, "No pending sessions to summarize.", { parseMode: "plain" });
16366
16746
  return;
16367
16747
  }
16368
- await channel.sendText(chatId, `Summarizing ${pendingIds.length} pending session(s)...`, "plain");
16748
+ await channel.sendText(chatId, `Summarizing ${pendingIds.length} pending session(s)...`, { parseMode: "plain" });
16369
16749
  await summarizeAllPending();
16370
- await channel.sendText(chatId, `Done. ${pendingIds.length} session(s) summarized and saved to memory.`, "plain");
16750
+ await channel.sendText(chatId, `Done. ${pendingIds.length} session(s) summarized and saved to memory.`, { parseMode: "plain" });
16371
16751
  } else if (action === "undo") {
16372
16752
  const pending = pendingSummaryUndo.get(chatId);
16373
16753
  if (!pending) {
16374
- await channel.sendText(chatId, "Undo window expired.", "plain");
16754
+ await channel.sendText(chatId, "Undo window expired.", { parseMode: "plain" });
16375
16755
  return;
16376
16756
  }
16377
16757
  clearTimeout(pending.timer);
@@ -16389,20 +16769,20 @@ Result: ${task.result.slice(0, 500)}` : ""
16389
16769
  if (action === "go") {
16390
16770
  const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
16391
16771
  const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16392
- await channel.sendText(chatId, "Analyzing recent interactions...", "plain");
16772
+ await channel.sendText(chatId, "Analyzing recent interactions...", { parseMode: "plain" });
16393
16773
  try {
16394
- const insights = await runAnalysis2(chatId);
16774
+ const insights = await runAnalysis2(chatId, { force: true });
16395
16775
  if (insights.length === 0) {
16396
- await channel.sendText(chatId, "No new insights from recent interactions.", "plain");
16776
+ await channel.sendText(chatId, "No new insights from recent interactions.", { parseMode: "plain" });
16397
16777
  } else {
16398
16778
  const items = insights.map((ins, i) => ({ id: i + 1, category: ins.category, insight: ins.insight }));
16399
- await channel.sendText(chatId, formatNightlySummary2(items) + "\n\nUse /evolve to review and apply proposals.", "plain");
16779
+ await channel.sendText(chatId, formatNightlySummary2(items) + "\n\nUse /evolve to review and apply proposals.", { parseMode: "plain" });
16400
16780
  }
16401
16781
  } catch (e) {
16402
- await channel.sendText(chatId, `Analysis failed: ${e}`, "plain");
16782
+ await channel.sendText(chatId, `Analysis failed: ${e}`, { parseMode: "plain" });
16403
16783
  }
16404
16784
  } else if (action === "cancel") {
16405
- await channel.sendText(chatId, "Reflection cancelled.", "plain");
16785
+ await channel.sendText(chatId, "Reflection cancelled.", { parseMode: "plain" });
16406
16786
  }
16407
16787
  return;
16408
16788
  } else if (data.startsWith("chats:")) {
@@ -16432,7 +16812,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16432
16812
  } else if (action === "remove") {
16433
16813
  const aliases = getAllChatAliases();
16434
16814
  if (aliases.length === 0) {
16435
- await channel.sendText(chatId, "No aliases to remove.", "plain");
16815
+ await channel.sendText(chatId, "No aliases to remove.", { parseMode: "plain" });
16436
16816
  return;
16437
16817
  }
16438
16818
  const aliasButtons = aliases.map((a, i) => [{ label: a.alias, data: `chats:remove:${i}` }]);
@@ -16442,12 +16822,12 @@ Result: ${task.result.slice(0, 500)}` : ""
16442
16822
  const idx = parseInt(action.slice(7), 10);
16443
16823
  const aliases = getAllChatAliases();
16444
16824
  if (isNaN(idx) || idx < 0 || idx >= aliases.length) {
16445
- await channel.sendText(chatId, "This data is outdated. Use /chats to refresh.", "plain");
16825
+ await channel.sendText(chatId, "This data is outdated. Use /chats to refresh.", { parseMode: "plain" });
16446
16826
  return;
16447
16827
  }
16448
16828
  const alias = aliases[idx].alias;
16449
16829
  const removed = removeChatAlias(alias);
16450
- await channel.sendText(chatId, removed ? `Alias "${alias}" removed.` : `Alias "${alias}" not found.`, "plain");
16830
+ await channel.sendText(chatId, removed ? `Alias "${alias}" removed.` : `Alias "${alias}" not found.`, { parseMode: "plain" });
16451
16831
  }
16452
16832
  return;
16453
16833
  } else if (data.startsWith("hist:")) {
@@ -16478,7 +16858,7 @@ Result: ${task.result.slice(0, 500)}` : ""
16478
16858
  [{ label: "Refresh List", data: "forget:page:1" }]
16479
16859
  ]);
16480
16860
  } else {
16481
- await channel.sendText(chatId, "This memory no longer exists.", "plain");
16861
+ await channel.sendText(chatId, "This memory no longer exists.", { parseMode: "plain" });
16482
16862
  }
16483
16863
  return;
16484
16864
  }
@@ -16499,10 +16879,10 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
16499
16879
  await channel.sendText(
16500
16880
  chatId,
16501
16881
  deleted ? "Memory deleted." : "Memory not found \u2014 already deleted.",
16502
- "plain"
16882
+ { parseMode: "plain" }
16503
16883
  );
16504
16884
  } else if (rest === "cancel") {
16505
- await channel.sendText(chatId, "Cancelled.", "plain");
16885
+ await channel.sendText(chatId, "Cancelled.", { parseMode: "plain" });
16506
16886
  }
16507
16887
  return;
16508
16888
  } else if (data.startsWith("mem:")) {
@@ -16519,7 +16899,7 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
16519
16899
  [{ label: "Refresh List", data: "mem:page:1" }]
16520
16900
  ]);
16521
16901
  } else {
16522
- await channel.sendText(chatId, "This memory no longer exists.", "plain");
16902
+ await channel.sendText(chatId, "This memory no longer exists.", { parseMode: "plain" });
16523
16903
  }
16524
16904
  return;
16525
16905
  }
@@ -16544,13 +16924,13 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
16544
16924
  await channel.sendText(
16545
16925
  chatId,
16546
16926
  deleted ? "Memory deleted." : "Memory not found \u2014 already deleted.",
16547
- "plain"
16927
+ { parseMode: "plain" }
16548
16928
  );
16549
16929
  } else if (rest.startsWith("forget:")) {
16550
16930
  const id = parseInt(rest.slice(7), 10);
16551
16931
  const memory2 = getMemoryById(id);
16552
16932
  if (!memory2) {
16553
- await channel.sendText(chatId, "Memory not found.", "plain");
16933
+ await channel.sendText(chatId, "Memory not found.", { parseMode: "plain" });
16554
16934
  return;
16555
16935
  }
16556
16936
  if (typeof channel.sendKeyboard === "function") {
@@ -16568,7 +16948,7 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
16568
16948
  }
16569
16949
  } else if (rest.startsWith("edit:")) {
16570
16950
  const id = parseInt(rest.slice(5), 10);
16571
- await channel.sendText(chatId, `Type: /memory edit ${id} <new content>`, "plain");
16951
+ await channel.sendText(chatId, `Type: /memory edit ${id} <new content>`, { parseMode: "plain" });
16572
16952
  } else if (rest === "back") {
16573
16953
  await sendMemoryPage(chatId, channel, 1);
16574
16954
  } else if (rest === "showall") {
@@ -16606,7 +16986,7 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
16606
16986
  if (rest === "undo") {
16607
16987
  const pending = pendingNewchatUndo.get(chatId);
16608
16988
  if (!pending) {
16609
- await channel.sendText(chatId, "Undo window expired.", "plain");
16989
+ await channel.sendText(chatId, "Undo window expired.", { parseMode: "plain" });
16610
16990
  return;
16611
16991
  }
16612
16992
  clearTimeout(pending.timer);
@@ -16637,13 +17017,13 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
16637
17017
  clearBackendLimit(bid, win);
16638
17018
  }
16639
17019
  }
16640
- await channel.sendText(chatId, "All usage limits cleared.", "plain");
17020
+ await channel.sendText(chatId, "All usage limits cleared.", { parseMode: "plain" });
16641
17021
  await sendUnifiedUsage(chatId, channel, "session");
16642
17022
  } else if (rest.startsWith("limits:set:")) {
16643
17023
  const bid = rest.slice(11);
16644
17024
  await channel.sendText(chatId, `Set limit for ${bid}:
16645
17025
  /limits ${bid} daily <tokens>
16646
- Example: /limits ${bid} daily 500000`, "plain");
17026
+ Example: /limits ${bid} daily 500000`, { parseMode: "plain" });
16647
17027
  }
16648
17028
  return;
16649
17029
  } else if (data.startsWith("job:")) {
@@ -16655,14 +17035,14 @@ Example: /limits ${bid} daily 500000`, "plain");
16655
17035
  await sendJobDetail(chatId, id, channel);
16656
17036
  } else if (rest.startsWith("run:")) {
16657
17037
  const id = parseInt(rest.slice(4), 10);
16658
- await channel.sendText(chatId, `Triggering job #${id}...`, "plain");
17038
+ await channel.sendText(chatId, `Triggering job #${id}...`, { parseMode: "plain" });
16659
17039
  const result = await triggerJob(id);
16660
17040
  if (typeof channel.sendKeyboard === "function") {
16661
17041
  await channel.sendKeyboard(chatId, result, [
16662
17042
  [{ label: "\u2190 Back to Job", data: `job:view:${id}` }]
16663
17043
  ]);
16664
17044
  } else {
16665
- await channel.sendText(chatId, result, "plain");
17045
+ await channel.sendText(chatId, result, { parseMode: "plain" });
16666
17046
  }
16667
17047
  } else if (rest.startsWith("pause:")) {
16668
17048
  const id = parseInt(rest.slice(6), 10);
@@ -16670,7 +17050,7 @@ Example: /limits ${bid} daily 500000`, "plain");
16670
17050
  if (paused) {
16671
17051
  await sendJobDetail(chatId, id, channel);
16672
17052
  } else {
16673
- await channel.sendText(chatId, `Job #${id} not found.`, "plain");
17053
+ await channel.sendText(chatId, `Job #${id} not found.`, { parseMode: "plain" });
16674
17054
  }
16675
17055
  } else if (rest.startsWith("resume:")) {
16676
17056
  const id = parseInt(rest.slice(7), 10);
@@ -16678,22 +17058,22 @@ Example: /limits ${bid} daily 500000`, "plain");
16678
17058
  if (resumed) {
16679
17059
  await sendJobDetail(chatId, id, channel);
16680
17060
  } else {
16681
- await channel.sendText(chatId, `Job #${id} not found.`, "plain");
17061
+ await channel.sendText(chatId, `Job #${id} not found.`, { parseMode: "plain" });
16682
17062
  }
16683
17063
  } else if (rest.startsWith("cancel:confirm:")) {
16684
17064
  const id = parseInt(rest.slice(15), 10);
16685
17065
  const cancelled = cancelJob(id);
16686
17066
  if (cancelled) {
16687
- await channel.sendText(chatId, `Job #${id} cancelled.`, "plain");
17067
+ await channel.sendText(chatId, `Job #${id} cancelled.`, { parseMode: "plain" });
16688
17068
  await sendJobsBoard(chatId, channel, 1);
16689
17069
  } else {
16690
- await channel.sendText(chatId, `Job #${id} not found.`, "plain");
17070
+ await channel.sendText(chatId, `Job #${id} not found.`, { parseMode: "plain" });
16691
17071
  }
16692
17072
  } else if (rest.startsWith("cancel:")) {
16693
17073
  const id = parseInt(rest.slice(7), 10);
16694
17074
  const job = getJobById(id);
16695
17075
  if (!job) {
16696
- await channel.sendText(chatId, `Job #${id} not found.`, "plain");
17076
+ await channel.sendText(chatId, `Job #${id} not found.`, { parseMode: "plain" });
16697
17077
  return;
16698
17078
  }
16699
17079
  if (typeof channel.sendKeyboard === "function") {
@@ -16747,7 +17127,7 @@ Command: ${command}`,
16747
17127
  );
16748
17128
  } else {
16749
17129
  await channel.sendText(chatId, `\u26A0\uFE0F Destructive command blocked: ${command}
16750
- No keyboard available to confirm.`, "plain");
17130
+ No keyboard available to confirm.`, { parseMode: "plain" });
16751
17131
  }
16752
17132
  return;
16753
17133
  }
@@ -16764,9 +17144,9 @@ No keyboard available to confirm.`, "plain");
16764
17144
  if (shouldSendAsFile(formatted)) {
16765
17145
  const buffer = Buffer.from(result.output, "utf-8");
16766
17146
  await channel.sendFile(chatId, buffer, "output.txt", "text/plain");
16767
- await channel.sendText(chatId, `Output too long (${result.output.length} chars), sent as file.`, "plain");
17147
+ await channel.sendText(chatId, `Output too long (${result.output.length} chars), sent as file.`, { parseMode: "plain" });
16768
17148
  } else {
16769
- await channel.sendText(chatId, formatted, "html");
17149
+ await channel.sendText(chatId, formatted, { parseMode: "html" });
16770
17150
  }
16771
17151
  }
16772
17152
  async function handleRawShell(command, chatId, channel, skipGuard = false) {
@@ -16785,7 +17165,7 @@ Command: ${command}`,
16785
17165
  );
16786
17166
  } else {
16787
17167
  await channel.sendText(chatId, `\u26A0\uFE0F Destructive command blocked: ${command}
16788
- No keyboard available to confirm.`, "plain");
17168
+ No keyboard available to confirm.`, { parseMode: "plain" });
16789
17169
  }
16790
17170
  return;
16791
17171
  }
@@ -16802,9 +17182,9 @@ No keyboard available to confirm.`, "plain");
16802
17182
  if (shouldSendAsFile(formatted)) {
16803
17183
  const buffer = Buffer.from(result.output, "utf-8");
16804
17184
  await channel.sendFile(chatId, buffer, "output.txt", "text/plain");
16805
- await channel.sendText(chatId, `Output too long (${result.output.length} chars), sent as file.`, "plain");
17185
+ await channel.sendText(chatId, `Output too long (${result.output.length} chars), sent as file.`, { parseMode: "plain" });
16806
17186
  } else {
16807
- await channel.sendText(chatId, formatted, "plain");
17187
+ await channel.sendText(chatId, formatted, { parseMode: "plain" });
16808
17188
  }
16809
17189
  }
16810
17190
  async function sendCwdSessionChoice(chatId, path, channel) {
@@ -16823,7 +17203,7 @@ What would you like to do?`,
16823
17203
  );
16824
17204
  } else {
16825
17205
  await channel.sendText(chatId, `Working directory set to: ${path}
16826
- Session kept.`, "plain");
17206
+ Session kept.`, { parseMode: "plain" });
16827
17207
  }
16828
17208
  }
16829
17209
  function parseIntervalToMs(input) {
@@ -16884,7 +17264,7 @@ async function sendSkillsPage(chatId, channel, skills2, page) {
16884
17264
  const header3 = totalPages > 1 ? `Skills (page ${safePage}/${totalPages}, ${skills2.length} total):` : "Available skills:";
16885
17265
  const footer = totalPages > 1 ? `
16886
17266
  Use /skills <page> to navigate (e.g. /skills 2)` : "";
16887
- await channel.sendText(chatId, [header3, "", ...lines, footer].join("\n"), "plain");
17267
+ await channel.sendText(chatId, [header3, "", ...lines, footer].join("\n"), { parseMode: "plain" });
16888
17268
  return;
16889
17269
  }
16890
17270
  const buttons = pageSkills.map((s) => {
@@ -16901,7 +17281,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
16901
17281
  const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
16902
17282
  await channel.sendKeyboard(chatId, header2, buttons);
16903
17283
  }
16904
- var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, pendingFallbackMessages, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
17284
+ var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, activeProposalDiscussion, pendingFallbackMessages, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
16905
17285
  var init_router = __esm({
16906
17286
  "src/router.ts"() {
16907
17287
  "use strict";
@@ -17040,6 +17420,9 @@ var init_router = __esm({
17040
17420
  ];
17041
17421
  pendingInterrupts = /* @__PURE__ */ new Map();
17042
17422
  bypassBusyCheck = /* @__PURE__ */ new Set();
17423
+ activeSideQuests = /* @__PURE__ */ new Map();
17424
+ MAX_SIDE_QUESTS = 2;
17425
+ activeProposalDiscussion = /* @__PURE__ */ new Map();
17043
17426
  pendingFallbackMessages = /* @__PURE__ */ new Map();
17044
17427
  dashboardClawWarnings = /* @__PURE__ */ new Map();
17045
17428
  pendingSummaryUndo = /* @__PURE__ */ new Map();
@@ -17502,9 +17885,64 @@ Use the CC-Claw CLI when you need to:
17502
17885
 
17503
17886
  ## Telegram Quick Reference
17504
17887
 
17505
- - \`/menu\` (\`/m\`) \u2014 Home screen keyboard with quick access to all major features
17506
- - \`/usage\` \u2014 Unified cost, limits, and usage view (\`/cost\` and \`/limits\` are aliases)
17507
- - \`@botname <query>\` \u2014 Inline mode: memory/history search from any Telegram chat (requires \`/setinline\` in BotFather)
17888
+ **Navigation & help:**
17889
+ - \`/menu\` (\`/m\`) \u2014 Home screen keyboard
17890
+ - \`/help\` (\`/start\`) \u2014 Help categories keyboard
17891
+ - \`/usage\` \u2014 Unified cost, limits, usage (\`/cost\` and \`/limits\` are aliases)
17892
+ - \`@botname <query>\` \u2014 Inline mode: memory/history search from any chat
17893
+
17894
+ **Session & history:**
17895
+ - \`/newchat\` (\`/new\`) \u2014 Start fresh conversation (with undo)
17896
+ - \`/summarize\` \u2014 Save session to memory (\`/summarize all\` for all pending)
17897
+ - \`/history\` \u2014 Recent messages (\`/history <query>\` for FTS search)
17898
+ - \`/stop\` \u2014 Cancel running task + any active side quests
17899
+
17900
+ **Side quests (parallel execution):**
17901
+ - \`sq: <message>\` or \`btw: <message>\` \u2014 Run in parallel while agent is busy (full tool access, shared context, max 2 concurrent, 5-min timeout)
17902
+ - Also available as "Side quest" button on the interrupt keyboard
17903
+
17904
+ **Backend & model:**
17905
+ - \`/backend\` \u2014 Backend picker keyboard
17906
+ - \`/claude\`, \`/gemini\`, \`/codex\`, \`/cursor\` \u2014 Switch directly
17907
+ - \`/model\` \u2014 Model picker keyboard
17908
+ - \`/thinking\` (\`/think\`) \u2014 Thinking level picker
17909
+
17910
+ **Settings keyboards:**
17911
+ - \`/permissions\` \u2014 Permission mode (yolo/safe/readonly/plan)
17912
+ - \`/tools\` \u2014 Enable/disable tools (safe mode)
17913
+ - \`/response_style\` \u2014 Concise / normal / detailed
17914
+ - \`/model_signature\` \u2014 Show/hide model name on responses
17915
+ - \`/agents mode\` \u2014 Agent mode (auto/native/claw)
17916
+ - \`/voice_config\` \u2014 Voice provider settings
17917
+
17918
+ **Memory:**
17919
+ - \`/remember <text>\` \u2014 Save a memory
17920
+ - \`/forget <keyword>\` \u2014 Delete memories
17921
+ - \`/memories\` \u2014 List memories
17922
+
17923
+ **Scheduling (interactive wizards):**
17924
+ - \`/schedule <description>\` \u2014 Create job via wizard
17925
+ - \`/jobs\` \u2014 Paginated job board
17926
+ - \`/editjob\` \u2014 Edit job wizard
17927
+ - \`/cancel\`, \`/pause\`, \`/resume\`, \`/run\`, \`/runs\` \u2014 Job action shortcuts
17928
+ - \`/health\` \u2014 Scheduler health
17929
+
17930
+ **Agents:**
17931
+ - \`/agents\` \u2014 Agent dashboard
17932
+ - \`/agents history\` \u2014 Recent sub-agent activity
17933
+ - \`/tasks\` \u2014 Task board
17934
+ - \`/stopagent\` \u2014 Interactive agent picker to stop
17935
+ - \`/stopall\` \u2014 Cancel all agents
17936
+ - \`/runners\` \u2014 List CLI runners
17937
+
17938
+ **Other:**
17939
+ - \`/imagine <prompt>\` (\`/image\`) \u2014 Generate image (requires GEMINI_API_KEY)
17940
+ - \`/skills\` \u2014 List skills
17941
+ - \`/skill-install <url>\` \u2014 Install skill from GitHub
17942
+ - \`/evolve\` \u2014 Self-learning interactive keyboard
17943
+ - \`/reflect\` \u2014 Trigger reflection analysis
17944
+ - \`/gemini_accounts\` \u2014 Gemini credential rotation management
17945
+ - \`/setup-profile\` \u2014 User profile setup wizard
17508
17946
 
17509
17947
  ## Command Reference
17510
17948
 
@@ -17516,6 +17954,7 @@ cc-claw doctor --json # Health checks (DB, CLIs, env, disk)
17516
17954
  cc-claw doctor --fix --json # Auto-repair common issues
17517
17955
  cc-claw logs -f # Follow daemon logs
17518
17956
  cc-claw logs --error # Show error log
17957
+ cc-claw logs --lines 50 # Show last N lines
17519
17958
  \`\`\`
17520
17959
 
17521
17960
  ### Backend & Model
@@ -17532,8 +17971,10 @@ cc-claw model set claude-opus-4-6 # Switch model
17532
17971
  \`\`\`bash
17533
17972
  cc-claw chat send "What is 2+2?" --json # Send message, get response
17534
17973
  cc-claw chat send "analyze this" --stream # Stream response tokens
17535
- cc-claw chat stop # Cancel running task
17536
- cc-claw tui # Interactive terminal chat
17974
+ cc-claw chat send "msg" --backend gemini --model gemini-3-flash-preview # Override backend/model
17975
+ cc-claw chat send "msg" --thinking high --cwd /path # Override thinking/cwd
17976
+ cc-claw chat stop # Cancel running task + side quests
17977
+ cc-claw tui --model claude-opus-4-6 # Interactive chat with model override
17537
17978
  \`\`\`
17538
17979
 
17539
17980
  ### Memory
@@ -17541,6 +17982,7 @@ cc-claw tui # Interactive terminal chat
17541
17982
  cc-claw memory list --json # All memories with salience
17542
17983
  cc-claw memory search "topic" --json # Search memories
17543
17984
  cc-claw memory history --json # Session summaries
17985
+ cc-claw memory history --limit 20 --json # Limit results
17544
17986
  cc-claw memory add "key" "value" # Save a memory (needs daemon)
17545
17987
  cc-claw memory forget "keyword" # Delete memories (needs daemon)
17546
17988
  \`\`\`
@@ -17555,7 +17997,8 @@ cc-claw session new # Clear session (newchat + summarize)
17555
17997
  \`\`\`bash
17556
17998
  cc-claw cron list --json # All scheduled jobs
17557
17999
  cc-claw cron health --json # Scheduler health
17558
- cc-claw cron runs --json # Run history
18000
+ cc-claw cron runs --json # Run history (all jobs)
18001
+ cc-claw cron runs 3 --limit 10 --json # Run history for job #3
17559
18002
  cc-claw cron create --description "Morning briefing" --cron "0 9 * * *" --backend claude
17560
18003
  cc-claw cron edit 3 --model gemini-3-flash-preview # Change job model
17561
18004
  cc-claw cron edit 3 --backend gemini --model gemini-3-flash-preview # Change backend + model
@@ -17572,6 +18015,7 @@ cc-claw cron run 3 # Trigger immediately
17572
18015
  \`\`\`bash
17573
18016
  cc-claw agents list --json # Active sub-agents
17574
18017
  cc-claw agents spawn --runner claude --task "review code" --json
18018
+ cc-claw agents spawn --runner gemini --task "analyze" --model gemini-3-flash-preview --role worker
17575
18019
  cc-claw agents cancel <id> # Cancel agent
17576
18020
  cc-claw agents cancel-all # Cancel all
17577
18021
  cc-claw tasks list --json # Task board
@@ -17584,6 +18028,8 @@ cc-claw config list --json # All runtime config
17584
18028
  cc-claw config get backend --json # Specific config value
17585
18029
  cc-claw config set backend gemini # Set config (needs daemon)
17586
18030
  cc-claw config env --json # Static .env values (redacted)
18031
+ cc-claw config response-style # Get current response style
18032
+ cc-claw config response-style detailed # Set response style (concise/normal/detailed)
17587
18033
  \`\`\`
17588
18034
 
17589
18035
  ### Usage & Cost
@@ -17656,6 +18102,37 @@ cc-claw summarizer set off # Disable summarization
17656
18102
  cc-claw summarizer set claude:claude-haiku-4-5 # Pin specific backend:model
17657
18103
  \`\`\`
17658
18104
 
18105
+ ### Gemini Credentials
18106
+ \`\`\`bash
18107
+ cc-claw gemini list --json # Show all credential slots (API keys + OAuth accounts)
18108
+ cc-claw gemini add-key --label "work" # Add an API key slot
18109
+ cc-claw gemini add-account --label "personal" # Add OAuth account (browser sign-in)
18110
+ cc-claw gemini remove <id-or-label> # Remove a credential slot
18111
+ cc-claw gemini enable <id-or-label> # Enable a disabled slot
18112
+ cc-claw gemini disable <id-or-label> # Disable a slot (skip in rotation)
18113
+ cc-claw gemini reorder <id-or-label> <priority> # Change rotation priority
18114
+ cc-claw gemini rotation # Get current rotation mode
18115
+ cc-claw gemini rotation all # Set mode (off/all/accounts/keys)
18116
+ \`\`\`
18117
+
18118
+ ### Self-Learning (evolve)
18119
+ \`\`\`bash
18120
+ cc-claw evolve --json # Status overview
18121
+ cc-claw evolve analyze # Trigger reflection analysis
18122
+ cc-claw evolve list --json # Pending proposals
18123
+ cc-claw evolve get <id> --json # Show proposal with diff
18124
+ cc-claw evolve apply <id> # Apply a proposal
18125
+ cc-claw evolve reject <id> # Reject a proposal
18126
+ cc-claw evolve undo <id> # Rollback applied insight
18127
+ cc-claw evolve on # Enable self-learning
18128
+ cc-claw evolve off # Disable self-learning
18129
+ cc-claw evolve model auto # Set analysis model (auto/pinned/cheap)
18130
+ cc-claw evolve model pinned --backend claude --model claude-haiku-4-5
18131
+ cc-claw evolve stats --days 30 --json # Growth report
18132
+ cc-claw evolve history --json # Applied/rejected insight history
18133
+ cc-claw evolve history --status applied --limit 10 # Filter history
18134
+ \`\`\`
18135
+
17659
18136
  ### Chat Aliases
17660
18137
  \`\`\`bash
17661
18138
  cc-claw chats list --json # Authorized chats and aliases
@@ -17678,8 +18155,7 @@ cc-claw mcps list --json # Registered MCP servers
17678
18155
  \`\`\`bash
17679
18156
  cc-claw db stats --json # Row counts, file size, WAL status
17680
18157
  cc-claw db path # Print DB file location
17681
- cc-claw db backup # Create backup
17682
- cc-claw db prune # Prune old data
18158
+ cc-claw db backup [path] # Create backup (optional destination)
17683
18159
  \`\`\`
17684
18160
 
17685
18161
  ### Service Management
@@ -17697,6 +18173,7 @@ cc-claw service uninstall # Remove service
17697
18173
  cc-claw setup # Interactive configuration wizard
17698
18174
  cc-claw tui # Interactive terminal chat
17699
18175
  cc-claw completion --shell zsh # Generate shell completions (bash/zsh/fish)
18176
+ cc-claw completion --shell zsh --install # Show install instructions
17700
18177
  cc-claw --ai # Generate/install SKILL.md for AI tools
17701
18178
  \`\`\`
17702
18179
 
@@ -17873,7 +18350,7 @@ async function main() {
17873
18350
  pendingSummarizeNotify = () => {
17874
18351
  if (primaryChatId) {
17875
18352
  for (const ch of channelRegistry.list()) {
17876
- ch.sendText(primaryChatId, "\u{1F504} Restarted \u2014 your conversations were summarized and saved.", "plain").catch(() => {
18353
+ ch.sendText(primaryChatId, "\u{1F504} Restarted \u2014 your conversations were summarized and saved.", { parseMode: "plain" }).catch(() => {
17877
18354
  });
17878
18355
  }
17879
18356
  }
@@ -17900,7 +18377,7 @@ async function main() {
17900
18377
  setNotifyCallback((chatId, message) => {
17901
18378
  notifyQueue = notifyQueue.then(async () => {
17902
18379
  for (const ch of channelRegistry.list()) {
17903
- await ch.sendText(chatId, `\u{1F916} ${message}`, "plain").catch(() => {
18380
+ await ch.sendText(chatId, `\u{1F916} ${message}`, { parseMode: "plain" }).catch(() => {
17904
18381
  });
17905
18382
  }
17906
18383
  await new Promise((r) => setTimeout(r, 300));