cc-claw 0.29.2 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +1464 -454
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var VERSION;
33
33
  var init_version = __esm({
34
34
  "src/version.ts"() {
35
35
  "use strict";
36
- VERSION = true ? "0.29.2" : (() => {
36
+ VERSION = true ? "0.30.0" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -806,6 +806,7 @@ __export(store_exports4, {
806
806
  getRejectedInsights: () => getRejectedInsights,
807
807
  getReviewSession: () => getReviewSession,
808
808
  getSignalCountForChat: () => getSignalCountForChat,
809
+ getSkillCorrections: () => getSkillCorrections,
809
810
  getUnprocessedSignalCount: () => getUnprocessedSignalCount,
810
811
  getUnprocessedSignals: () => getUnprocessedSignals,
811
812
  initReflectionTables: () => initReflectionTables,
@@ -872,11 +873,13 @@ function initReflectionTables(db3) {
872
873
  backend TEXT,
873
874
  model TEXT,
874
875
  thinkingLevel TEXT,
876
+ skill_name TEXT,
875
877
  processedAt TEXT,
876
878
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
877
879
  );
878
880
  CREATE INDEX IF NOT EXISTS idx_signals_chat_time ON feedback_signals (chatId, created_at DESC);
879
881
  CREATE INDEX IF NOT EXISTS idx_signals_unprocessed ON feedback_signals (processedAt) WHERE processedAt IS NULL;
882
+ CREATE INDEX IF NOT EXISTS idx_signals_skill ON feedback_signals (skill_name) WHERE skill_name IS NOT NULL;
880
883
 
881
884
  CREATE TABLE IF NOT EXISTS insights (
882
885
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -949,11 +952,17 @@ function initReflectionTables(db3) {
949
952
  cleanupReflectionData(db3);
950
953
  const settings = getReflectionSettings(db3, "global");
951
954
  cleanupBackupFiles(CC_CLAW_HOME, settings.backupRetentionDays);
955
+ try {
956
+ db3.prepare("SELECT skill_name FROM feedback_signals LIMIT 0").get();
957
+ } catch {
958
+ db3.exec("ALTER TABLE feedback_signals ADD COLUMN skill_name TEXT");
959
+ db3.exec("CREATE INDEX IF NOT EXISTS idx_signals_skill ON feedback_signals (skill_name) WHERE skill_name IS NOT NULL");
960
+ }
952
961
  }
953
962
  function logSignal(db3, params) {
954
963
  const result = db3.prepare(`
955
- INSERT INTO feedback_signals (chatId, signalType, trigger, context, source, confidence, backend, model, thinkingLevel)
956
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
964
+ INSERT INTO feedback_signals (chatId, signalType, trigger, context, source, confidence, backend, model, thinkingLevel, skill_name)
965
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
957
966
  `).run(
958
967
  params.chatId,
959
968
  params.signalType,
@@ -963,7 +972,8 @@ function logSignal(db3, params) {
963
972
  params.confidence,
964
973
  params.backend ?? null,
965
974
  params.model ?? null,
966
- params.thinkingLevel ?? null
975
+ params.thinkingLevel ?? null,
976
+ params.skillName ?? null
967
977
  );
968
978
  return Number(result.lastInsertRowid);
969
979
  }
@@ -1004,6 +1014,56 @@ function getLastAnalysisTime(db3, chatId) {
1004
1014
  `).get(chatId);
1005
1015
  return row.lastTime;
1006
1016
  }
1017
+ function getSkillCorrections(db3, windowDays = 30, minCorrections = 3) {
1018
+ const skillRows = db3.prepare(`
1019
+ SELECT
1020
+ skill_name,
1021
+ COUNT(*) AS cnt,
1022
+ GROUP_CONCAT(context, '|||') AS contexts_raw
1023
+ FROM feedback_signals
1024
+ WHERE
1025
+ signalType = 'correction'
1026
+ AND skill_name IS NOT NULL
1027
+ AND created_at >= datetime('now', '-' || ? || ' days')
1028
+ GROUP BY skill_name
1029
+ HAVING cnt >= ?
1030
+ ORDER BY cnt DESC
1031
+ `).all(windowDays, minCorrections);
1032
+ if (skillRows.length === 0) return [];
1033
+ const skillNames = skillRows.map((r) => r.skill_name);
1034
+ const placeholders = skillNames.map(() => "?").join(", ");
1035
+ const breakdownRows = db3.prepare(`
1036
+ SELECT
1037
+ skill_name,
1038
+ model,
1039
+ COUNT(*) AS cnt
1040
+ FROM feedback_signals
1041
+ WHERE
1042
+ signalType = 'correction'
1043
+ AND skill_name IN (${placeholders})
1044
+ AND model IS NOT NULL
1045
+ AND created_at >= datetime('now', '-' || ? || ' days')
1046
+ GROUP BY skill_name, model
1047
+ ORDER BY skill_name, cnt DESC
1048
+ `).all(...skillNames, windowDays);
1049
+ const breakdownMap = /* @__PURE__ */ new Map();
1050
+ for (const row of breakdownRows) {
1051
+ if (!breakdownMap.has(row.skill_name)) {
1052
+ breakdownMap.set(row.skill_name, []);
1053
+ }
1054
+ breakdownMap.get(row.skill_name).push({ model: row.model, count: row.cnt });
1055
+ }
1056
+ return skillRows.map((row) => {
1057
+ const rawContexts = row.contexts_raw ? row.contexts_raw.split("|||") : [];
1058
+ const contexts = rawContexts.filter((c) => c && c.trim().length > 0);
1059
+ return {
1060
+ skillName: row.skill_name,
1061
+ correctionCount: row.cnt,
1062
+ contexts,
1063
+ modelBreakdown: breakdownMap.get(row.skill_name) ?? []
1064
+ };
1065
+ });
1066
+ }
1007
1067
  function getReflectionStatus(db3, chatId) {
1008
1068
  const row = db3.prepare("SELECT status FROM chat_reflection WHERE chatId = ?").get(chatId);
1009
1069
  return row?.status ?? "frozen";
@@ -2009,6 +2069,12 @@ function initSchema(db3) {
2009
2069
  value INTEGER NOT NULL DEFAULT 0
2010
2070
  );
2011
2071
  `);
2072
+ db3.exec(`
2073
+ CREATE TABLE IF NOT EXISTS chat_pinned_skills (
2074
+ chat_id TEXT PRIMARY KEY,
2075
+ skills TEXT NOT NULL DEFAULT '[]'
2076
+ );
2077
+ `);
2012
2078
  db3.exec(`
2013
2079
  CREATE TABLE IF NOT EXISTS backend_credentials (
2014
2080
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -2091,17 +2157,48 @@ function applySalienceDecay(db3) {
2091
2157
  db3.prepare("DELETE FROM session_summaries WHERE salience < 0.1").run();
2092
2158
  db3.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('last_salience_decay', ?)").run(now);
2093
2159
  }
2160
+ function _setSchemaDbGetter(getter) {
2161
+ _getDb = getter;
2162
+ }
2094
2163
  function getMetaValue(key) {
2095
- const { getDb: getDb2 } = (init_store5(), __toCommonJS(store_exports5));
2096
- const db3 = getDb2();
2164
+ if (!_getDb) throw new Error("[schema] DB getter not initialized \u2014 was _setSchemaDbGetter() called?");
2165
+ const db3 = _getDb();
2097
2166
  const row = db3.prepare("SELECT value FROM meta WHERE key = ?").get(key);
2098
2167
  return row?.value;
2099
2168
  }
2100
2169
  function setMetaValue(key, value) {
2101
- const { getDb: getDb2 } = (init_store5(), __toCommonJS(store_exports5));
2102
- const db3 = getDb2();
2170
+ if (!_getDb) throw new Error("[schema] DB getter not initialized \u2014 was _setSchemaDbGetter() called?");
2171
+ const db3 = _getDb();
2103
2172
  db3.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, value);
2104
2173
  }
2174
+ function getDisabledBackends() {
2175
+ if (_disabledCache) return _disabledCache;
2176
+ const json = getMetaValue(DISABLED_BACKENDS_KEY);
2177
+ if (!json) {
2178
+ _disabledCache = [];
2179
+ return _disabledCache;
2180
+ }
2181
+ try {
2182
+ _disabledCache = JSON.parse(json);
2183
+ return _disabledCache;
2184
+ } catch {
2185
+ _disabledCache = [];
2186
+ return _disabledCache;
2187
+ }
2188
+ }
2189
+ function setBackendDisabled(backendId, disabled) {
2190
+ const current = [...getDisabledBackends()];
2191
+ if (disabled) {
2192
+ if (!current.includes(backendId)) {
2193
+ current.push(backendId);
2194
+ }
2195
+ } else {
2196
+ const idx = current.indexOf(backendId);
2197
+ if (idx !== -1) current.splice(idx, 1);
2198
+ }
2199
+ setMetaValue(DISABLED_BACKENDS_KEY, JSON.stringify(current));
2200
+ _disabledCache = current;
2201
+ }
2105
2202
  function backfillJobTitles(database) {
2106
2203
  const rows = database.prepare(
2107
2204
  "SELECT id, description FROM jobs WHERE active = 1 AND (title IS NULL OR title LIKE 'No thinking%' OR title LIKE 'You must%' OR title LIKE 'You MUST%')"
@@ -2125,6 +2222,7 @@ function backfillJobTitles(database) {
2125
2222
  database.prepare("UPDATE jobs SET title = ? WHERE id = ?").run(title, row.id);
2126
2223
  }
2127
2224
  }
2225
+ var _getDb, DISABLED_BACKENDS_KEY, _disabledCache;
2128
2226
  var init_schema = __esm({
2129
2227
  "src/memory/schema.ts"() {
2130
2228
  "use strict";
@@ -2135,6 +2233,9 @@ var init_schema = __esm({
2135
2233
  init_store4();
2136
2234
  init_log();
2137
2235
  init_classify();
2236
+ _getDb = null;
2237
+ DISABLED_BACKENDS_KEY = "disabled_backends";
2238
+ _disabledCache = null;
2138
2239
  }
2139
2240
  });
2140
2241
 
@@ -2813,13 +2914,16 @@ __export(chat_settings_exports, {
2813
2914
  GLOBAL_SUMMARIZER_SENTINEL: () => GLOBAL_SUMMARIZER_SENTINEL,
2814
2915
  clearAgentMode: () => clearAgentMode,
2815
2916
  clearAllPaidSlots: () => clearAllPaidSlots,
2917
+ clearAllSummarizerOverrides: () => clearAllSummarizerOverrides,
2816
2918
  clearChatPaidSlots: () => clearChatPaidSlots,
2817
2919
  clearCwd: () => clearCwd,
2818
2920
  clearExecMode: () => clearExecMode,
2819
2921
  clearModel: () => clearModel,
2820
2922
  clearModelMap: () => clearModelMap,
2923
+ clearPinnedSkills: () => clearPinnedSkills,
2821
2924
  clearSummarizer: () => clearSummarizer,
2822
2925
  clearThinkingLevel: () => clearThinkingLevel,
2926
+ countSummarizerOverrides: () => countSummarizerOverrides,
2823
2927
  deleteBookmark: () => deleteBookmark,
2824
2928
  determineEscalationTarget: () => determineEscalationTarget,
2825
2929
  findBookmarksByPrefix: () => findBookmarksByPrefix,
@@ -2835,11 +2939,13 @@ __export(chat_settings_exports, {
2835
2939
  getMode: () => getMode,
2836
2940
  getModel: () => getModel,
2837
2941
  getPendingEscalation: () => getPendingEscalation,
2942
+ getPinnedSkills: () => getPinnedSkills,
2838
2943
  getRecentBookmarks: () => getRecentBookmarks,
2839
2944
  getSessionLogEnabled: () => getSessionLogEnabled,
2840
2945
  getShowThinkingUi: () => getShowThinkingUi,
2841
2946
  getSkillSuggestionsEnabled: () => getSkillSuggestionsEnabled,
2842
2947
  getSummarizer: () => getSummarizer,
2948
+ getSummarizerWithSource: () => getSummarizerWithSource,
2843
2949
  getThinkingLevel: () => getThinkingLevel,
2844
2950
  getToolsMap: () => getToolsMap,
2845
2951
  getVerboseLevel: () => getVerboseLevel,
@@ -2853,6 +2959,7 @@ __export(chat_settings_exports, {
2853
2959
  setGlobalSummarizer: () => setGlobalSummarizer,
2854
2960
  setMode: () => setMode,
2855
2961
  setModel: () => setModel,
2962
+ setPinnedSkills: () => setPinnedSkills,
2856
2963
  setSessionLogEnabled: () => setSessionLogEnabled,
2857
2964
  setShowThinkingUi: () => setShowThinkingUi,
2858
2965
  setSkillSuggestionsEnabled: () => setSkillSuggestionsEnabled,
@@ -3184,6 +3291,35 @@ function setSummarizer(chatId, backend2, model2) {
3184
3291
  function clearSummarizer(chatId) {
3185
3292
  getDb().prepare("DELETE FROM chat_summarizer WHERE chat_id = ?").run(chatId);
3186
3293
  }
3294
+ function clearAllSummarizerOverrides() {
3295
+ const result = getDb().prepare(
3296
+ "DELETE FROM chat_summarizer WHERE chat_id != ?"
3297
+ ).run(GLOBAL_SUMMARIZER_SENTINEL);
3298
+ return result.changes;
3299
+ }
3300
+ function getSummarizerWithSource(chatId) {
3301
+ const perChat = getDb().prepare(
3302
+ "SELECT backend, model FROM chat_summarizer WHERE chat_id = ?"
3303
+ ).get(chatId);
3304
+ const globalRow = getDb().prepare(
3305
+ "SELECT backend, model FROM chat_summarizer WHERE chat_id = ?"
3306
+ ).get(GLOBAL_SUMMARIZER_SENTINEL);
3307
+ const hasPerChat = perChat && (perChat.backend || perChat.model);
3308
+ const globalConfig = globalRow && (globalRow.backend || globalRow.model) ? globalRow : { backend: null, model: null };
3309
+ if (hasPerChat) {
3310
+ return { config: perChat, source: "per-chat", globalConfig };
3311
+ }
3312
+ if (globalConfig.backend || globalConfig.model) {
3313
+ return { config: globalConfig, source: "global", globalConfig };
3314
+ }
3315
+ return { config: { backend: null, model: null }, source: "auto", globalConfig };
3316
+ }
3317
+ function countSummarizerOverrides() {
3318
+ const row = getDb().prepare(
3319
+ "SELECT COUNT(*) as cnt FROM chat_summarizer WHERE chat_id != ?"
3320
+ ).get(GLOBAL_SUMMARIZER_SENTINEL);
3321
+ return row.cnt;
3322
+ }
3187
3323
  function getAgentMode(chatId) {
3188
3324
  const row = getDb().prepare("SELECT mode FROM chat_agent_mode WHERE chat_id = ?").get(chatId);
3189
3325
  return row?.mode ?? "auto";
@@ -3233,6 +3369,27 @@ function toggleSessionLogEnabled(chatId) {
3233
3369
  setSessionLogEnabled(chatId, next);
3234
3370
  return next;
3235
3371
  }
3372
+ function getPinnedSkills(chatId) {
3373
+ const row = getDb().prepare(
3374
+ "SELECT skills FROM chat_pinned_skills WHERE chat_id = ?"
3375
+ ).get(chatId);
3376
+ if (!row?.skills) return [];
3377
+ try {
3378
+ return JSON.parse(row.skills);
3379
+ } catch {
3380
+ return [];
3381
+ }
3382
+ }
3383
+ function setPinnedSkills(chatId, skills2) {
3384
+ getDb().prepare(`
3385
+ INSERT INTO chat_pinned_skills (chat_id, skills)
3386
+ VALUES (?, ?)
3387
+ ON CONFLICT(chat_id) DO UPDATE SET skills = ?
3388
+ `).run(chatId, JSON.stringify(skills2), JSON.stringify(skills2));
3389
+ }
3390
+ function clearPinnedSkills(chatId) {
3391
+ getDb().prepare("DELETE FROM chat_pinned_skills WHERE chat_id = ?").run(chatId);
3392
+ }
3236
3393
  function getAllowPaidSlots(chatId, backend2) {
3237
3394
  const row = getDb().prepare(
3238
3395
  "SELECT 1 FROM chat_allow_paid_slots WHERE chat_id = ? AND backend = ?"
@@ -3325,7 +3482,7 @@ function getUsage(chatId) {
3325
3482
  }
3326
3483
  function addUsage(chatId, input, output2, cacheRead, model2, backend2, contextSize) {
3327
3484
  const db3 = getDb();
3328
- const finalContextSize = contextSize ?? input + cacheRead;
3485
+ const finalContextSize = contextSize === null ? 0 : contextSize ?? input + cacheRead;
3329
3486
  db3.prepare(`
3330
3487
  INSERT INTO chat_usage (chat_id, input_tokens, output_tokens, cache_read_tokens, request_count, last_input_tokens, last_cache_read_tokens, context_size, updated_at)
3331
3488
  VALUES (?, ?, ?, ?, 1, ?, ?, ?, datetime('now'))
@@ -3616,6 +3773,7 @@ __export(jobs_exports, {
3616
3773
  cancelJobById: () => cancelJobById,
3617
3774
  completeJobRun: () => completeJobRun,
3618
3775
  getActiveJobs: () => getActiveJobs,
3776
+ getActiveJobsByBackend: () => getActiveJobsByBackend,
3619
3777
  getAllJobs: () => getAllJobs,
3620
3778
  getJobById: () => getJobById,
3621
3779
  getJobRuns: () => getJobRuns,
@@ -3623,6 +3781,7 @@ __export(jobs_exports, {
3623
3781
  insertJob: () => insertJob,
3624
3782
  insertJobRun: () => insertJobRun,
3625
3783
  pruneJobRuns: () => pruneJobRuns,
3784
+ reassignJobsBackend: () => reassignJobsBackend,
3626
3785
  resetJobFailures: () => resetJobFailures,
3627
3786
  updateJob: () => updateJob,
3628
3787
  updateJobEnabled: () => updateJobEnabled,
@@ -3679,6 +3838,15 @@ function getActiveJobs() {
3679
3838
  function getAllJobs() {
3680
3839
  return getDb().prepare(`${JOB_SELECT} WHERE active = 1 ORDER BY id`).all().map((r) => mapJobRow(r));
3681
3840
  }
3841
+ function getActiveJobsByBackend(backendId) {
3842
+ return getDb().prepare(`${JOB_SELECT} WHERE active = 1 AND enabled = 1 AND backend = ?`).all(backendId).map((r) => mapJobRow(r));
3843
+ }
3844
+ function reassignJobsBackend(fromBackend, toBackend) {
3845
+ const result = getDb().prepare(
3846
+ "UPDATE jobs SET backend = ? WHERE active = 1 AND enabled = 1 AND backend = ?"
3847
+ ).run(toBackend, fromBackend);
3848
+ return result.changes;
3849
+ }
3682
3850
  function updateJobEnabled(id, enabled) {
3683
3851
  const result = getDb().prepare("UPDATE jobs SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, id);
3684
3852
  return result.changes > 0;
@@ -4593,6 +4761,12 @@ var init_session_log = __esm({
4593
4761
  });
4594
4762
 
4595
4763
  // src/memory/api-context.ts
4764
+ var api_context_exports = {};
4765
+ __export(api_context_exports, {
4766
+ buildApiMessages: () => buildApiMessages,
4767
+ estimateContextUsage: () => estimateContextUsage,
4768
+ estimateTokens: () => estimateTokens
4769
+ });
4596
4770
  import { getEncoding } from "js-tiktoken";
4597
4771
  function estimateTokens(text) {
4598
4772
  return enc.encode(text).length;
@@ -4621,7 +4795,7 @@ async function buildApiMessages(chatId, userMessage, systemPrompt, contextWindow
4621
4795
  return { role: "assistant", content: entry.text };
4622
4796
  });
4623
4797
  const currentUserMessage = { role: "user", content: userMessage };
4624
- const tokenBudget = Math.floor(contextWindow * 0.85);
4798
+ const tokenBudget = Math.floor(contextWindow * 0.95);
4625
4799
  const fixedMessages = [systemMessage, currentUserMessage];
4626
4800
  const fixedTokens = fixedMessages.reduce((sum, m) => sum + enc.encode(typeof m.content === "string" ? m.content : JSON.stringify(m.content)).length, 0);
4627
4801
  const historyBudget = tokenBudget - fixedTokens;
@@ -4637,12 +4811,30 @@ async function buildApiMessages(chatId, userMessage, systemPrompt, contextWindow
4637
4811
  }
4638
4812
  return [systemMessage, ...truncatedHistory, currentUserMessage];
4639
4813
  }
4640
- var enc;
4814
+ function estimateContextUsage(chatId, contextWindow) {
4815
+ const logEntries = getLog(chatId);
4816
+ if (logEntries.length === 0) {
4817
+ estimateCache.delete(chatId);
4818
+ return { estimatedTokens: 0, contextWindow, percentage: 0 };
4819
+ }
4820
+ const cached = estimateCache.get(chatId);
4821
+ if (cached && cached.logSize === logEntries.length) {
4822
+ const percentage2 = contextWindow > 0 ? cached.tokens / contextWindow * 100 : 0;
4823
+ return { estimatedTokens: cached.tokens, contextWindow, percentage: percentage2 };
4824
+ }
4825
+ const text = logEntries.map((e) => e.text).join("\n");
4826
+ const estimatedTokens = estimateTokens(text);
4827
+ estimateCache.set(chatId, { logSize: logEntries.length, tokens: estimatedTokens });
4828
+ const percentage = contextWindow > 0 ? estimatedTokens / contextWindow * 100 : 0;
4829
+ return { estimatedTokens, contextWindow, percentage };
4830
+ }
4831
+ var enc, estimateCache;
4641
4832
  var init_api_context = __esm({
4642
4833
  "src/memory/api-context.ts"() {
4643
4834
  "use strict";
4644
4835
  init_session_log();
4645
4836
  enc = getEncoding("cl100k_base");
4837
+ estimateCache = /* @__PURE__ */ new Map();
4646
4838
  }
4647
4839
  });
4648
4840
 
@@ -4838,6 +5030,7 @@ __export(store_exports5, {
4838
5030
  clearAgentMode: () => clearAgentMode,
4839
5031
  clearAllPaidSlots: () => clearAllPaidSlots,
4840
5032
  clearAllSessions: () => clearAllSessions,
5033
+ clearAllSummarizerOverrides: () => clearAllSummarizerOverrides,
4841
5034
  clearBackendLimit: () => clearBackendLimit,
4842
5035
  clearChatBackendSlot: () => clearChatBackendSlot,
4843
5036
  clearChatGeminiSlot: () => clearChatGeminiSlot,
@@ -4847,11 +5040,13 @@ __export(store_exports5, {
4847
5040
  clearMessageLog: () => clearMessageLog,
4848
5041
  clearModel: () => clearModel,
4849
5042
  clearModelMap: () => clearModelMap,
5043
+ clearPinnedSkills: () => clearPinnedSkills,
4850
5044
  clearSession: () => clearSession,
4851
5045
  clearSummarizer: () => clearSummarizer,
4852
5046
  clearThinkingLevel: () => clearThinkingLevel,
4853
5047
  clearUsage: () => clearUsage,
4854
5048
  completeJobRun: () => completeJobRun,
5049
+ countSummarizerOverrides: () => countSummarizerOverrides,
4855
5050
  deleteBookmark: () => deleteBookmark,
4856
5051
  deleteMemoryById: () => deleteMemoryById,
4857
5052
  deleteSessionSummary: () => deleteSessionSummary,
@@ -4864,6 +5059,7 @@ __export(store_exports5, {
4864
5059
  forget: () => forget,
4865
5060
  forgetMemory: () => forgetMemory,
4866
5061
  getActiveJobs: () => getActiveJobs,
5062
+ getActiveJobsByBackend: () => getActiveJobsByBackend,
4867
5063
  getActiveWatches: () => getActiveWatches,
4868
5064
  getAgentMode: () => getAgentMode,
4869
5065
  getAllBackendLimits: () => getAllBackendLimits,
@@ -4890,6 +5086,7 @@ __export(store_exports5, {
4890
5086
  getChatUsageByModel: () => getChatUsageByModel,
4891
5087
  getCwd: () => getCwd,
4892
5088
  getDb: () => getDb,
5089
+ getDisabledBackends: () => getDisabledBackends,
4893
5090
  getEligibleBackendSlots: () => getEligibleBackendSlots,
4894
5091
  getEligibleGeminiSlots: () => getEligibleGeminiSlots,
4895
5092
  getEnabledTools: () => getEnabledTools,
@@ -4911,6 +5108,7 @@ __export(store_exports5, {
4911
5108
  getNextBackendSlot: () => getNextBackendSlot,
4912
5109
  getNextGeminiSlot: () => getNextGeminiSlot,
4913
5110
  getPendingEscalation: () => getPendingEscalation,
5111
+ getPinnedSkills: () => getPinnedSkills,
4914
5112
  getRecentBookmarks: () => getRecentBookmarks,
4915
5113
  getRecentMemories: () => getRecentMemories,
4916
5114
  getRecentMessageLog: () => getRecentMessageLog,
@@ -4923,6 +5121,7 @@ __export(store_exports5, {
4923
5121
  getShowThinkingUi: () => getShowThinkingUi,
4924
5122
  getSkillSuggestionsEnabled: () => getSkillSuggestionsEnabled,
4925
5123
  getSummarizer: () => getSummarizer,
5124
+ getSummarizerWithSource: () => getSummarizerWithSource,
4926
5125
  getThinkingLevel: () => getThinkingLevel,
4927
5126
  getToolsMap: () => getToolsMap,
4928
5127
  getUnsummarizedChatIds: () => getUnsummarizedChatIds,
@@ -4951,6 +5150,7 @@ __export(store_exports5, {
4951
5150
  pruneJobRuns: () => pruneJobRuns,
4952
5151
  pruneMessageLog: () => pruneMessageLog,
4953
5152
  queueHalfLifeExtension: () => queueHalfLifeExtension,
5153
+ reassignJobsBackend: () => reassignJobsBackend,
4954
5154
  recall: () => recall,
4955
5155
  reenableBackendSlot: () => reenableBackendSlot,
4956
5156
  reenableSlot: () => reenableSlot,
@@ -4982,6 +5182,7 @@ __export(store_exports5, {
4982
5182
  setAgentMode: () => setAgentMode,
4983
5183
  setAllowPaidSlots: () => setAllowPaidSlots,
4984
5184
  setBackend: () => setBackend,
5185
+ setBackendDisabled: () => setBackendDisabled,
4985
5186
  setBackendLimit: () => setBackendLimit,
4986
5187
  setBackendRotationMode: () => setBackendRotationMode,
4987
5188
  setBackendSlotEnabled: () => setBackendSlotEnabled,
@@ -4997,6 +5198,7 @@ __export(store_exports5, {
4997
5198
  setMode: () => setMode,
4998
5199
  setModel: () => setModel,
4999
5200
  setModelSignature: () => setModelSignature,
5201
+ setPinnedSkills: () => setPinnedSkills,
5000
5202
  setResponseStyle: () => setResponseStyle,
5001
5203
  setSessionId: () => setSessionId,
5002
5204
  setSessionLogEnabled: () => setSessionLogEnabled,
@@ -5028,6 +5230,7 @@ function initDatabase() {
5028
5230
  db = new Database(DB_PATH);
5029
5231
  db.pragma("journal_mode = WAL");
5030
5232
  db.pragma("foreign_keys = ON");
5233
+ _setSchemaDbGetter(() => db);
5031
5234
  initSchema(db);
5032
5235
  startMaintenanceTimers(db);
5033
5236
  }
@@ -5043,6 +5246,7 @@ function getDb() {
5043
5246
  function _setDbForTesting(testDb) {
5044
5247
  db = testDb;
5045
5248
  _testDbPath = testDb.name;
5249
+ _setSchemaDbGetter(() => db);
5046
5250
  }
5047
5251
  var db, _testDbPath;
5048
5252
  var init_store5 = __esm({
@@ -7559,6 +7763,52 @@ var init_html = __esm({
7559
7763
  }
7560
7764
  });
7561
7765
 
7766
+ // src/format-time.ts
7767
+ function formatLocalDate(utcDatetime) {
7768
+ const d = parseUtcDatetime(utcDatetime);
7769
+ if (!d) return utcDatetime.split("T")[0] ?? utcDatetime.split(" ")[0];
7770
+ const year = d.getFullYear();
7771
+ const month = String(d.getMonth() + 1).padStart(2, "0");
7772
+ const day = String(d.getDate()).padStart(2, "0");
7773
+ return `${year}-${month}-${day}`;
7774
+ }
7775
+ function formatLocalDateTime(utcDatetime) {
7776
+ const d = parseUtcDatetime(utcDatetime);
7777
+ if (!d) return utcDatetime;
7778
+ const year = d.getFullYear();
7779
+ const month = String(d.getMonth() + 1).padStart(2, "0");
7780
+ const day = String(d.getDate()).padStart(2, "0");
7781
+ const hours = String(d.getHours()).padStart(2, "0");
7782
+ const minutes = String(d.getMinutes()).padStart(2, "0");
7783
+ const tzAbbr = getTimezoneAbbr(d);
7784
+ return `${year}-${month}-${day} ${hours}:${minutes} ${tzAbbr}`;
7785
+ }
7786
+ function parseUtcDatetime(utcDatetime) {
7787
+ if (!utcDatetime) return null;
7788
+ const normalized = utcDatetime.includes("T") ? utcDatetime : utcDatetime.replace(" ", "T");
7789
+ const withZ = normalized.endsWith("Z") ? normalized : normalized + "Z";
7790
+ const d = new Date(withZ);
7791
+ return isNaN(d.getTime()) ? null : d;
7792
+ }
7793
+ function getTimezoneAbbr(date) {
7794
+ try {
7795
+ const parts = new Intl.DateTimeFormat("en-US", {
7796
+ timeZone: systemTimezone,
7797
+ timeZoneName: "short"
7798
+ }).formatToParts(date);
7799
+ return parts.find((p) => p.type === "timeZoneName")?.value ?? "";
7800
+ } catch {
7801
+ return "";
7802
+ }
7803
+ }
7804
+ var systemTimezone;
7805
+ var init_format_time = __esm({
7806
+ "src/format-time.ts"() {
7807
+ "use strict";
7808
+ systemTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
7809
+ }
7810
+ });
7811
+
7562
7812
  // src/channels/telegram-throttle.ts
7563
7813
  var telegram_throttle_exports = {};
7564
7814
  __export(telegram_throttle_exports, {
@@ -7581,13 +7831,15 @@ function is429(err) {
7581
7831
  function sleep(ms) {
7582
7832
  return new Promise((r) => setTimeout(r, ms));
7583
7833
  }
7584
- var PER_DM_INTERVAL_MS, PER_GROUP_INTERVAL_MS, GLOBAL_INTERVAL_MS, MAX_RETRIES2, RETRY_DELAY_MS, MAX_QUEUE_SIZE, EDIT_PRESSURE_THRESHOLD, MAX_PER_CHAT_QUEUE, MAX_TOTAL_PAUSE_MS, CIRCUIT_TRIP_THRESHOLD, CIRCUIT_TRIP_WINDOW_MS, CIRCUIT_COOLDOWN_STEP_SEC, CIRCUIT_RESET_WINDOW_MS, CircuitState, Priority, _activeThrottle, TelegramThrottle;
7834
+ var PER_DM_INTERVAL_MS, PER_GROUP_INTERVAL_MS, P0_PACING_MS, GLOBAL_INTERVAL_MS, MAX_RETRIES2, RETRY_DELAY_MS, MAX_QUEUE_SIZE, EDIT_PRESSURE_THRESHOLD, MAX_PER_CHAT_QUEUE, MAX_TOTAL_PAUSE_MS, CIRCUIT_TRIP_THRESHOLD, CIRCUIT_TRIP_WINDOW_MS, CIRCUIT_COOLDOWN_STEP_SEC, CIRCUIT_RESET_WINDOW_MS, CircuitState, Priority, _activeThrottle, TelegramThrottle;
7585
7835
  var init_telegram_throttle = __esm({
7586
7836
  "src/channels/telegram-throttle.ts"() {
7587
7837
  "use strict";
7588
7838
  init_log();
7839
+ init_format_time();
7589
7840
  PER_DM_INTERVAL_MS = 1e3;
7590
7841
  PER_GROUP_INTERVAL_MS = 3500;
7842
+ P0_PACING_MS = 150;
7591
7843
  GLOBAL_INTERVAL_MS = 100;
7592
7844
  MAX_RETRIES2 = 2;
7593
7845
  RETRY_DELAY_MS = 1e3;
@@ -7766,16 +8018,17 @@ var init_telegram_throttle = __esm({
7766
8018
  while (this.queue.length > 0) {
7767
8019
  while (this.isPaused()) {
7768
8020
  if (this.pauseStartedAt > 0 && Date.now() - this.pauseStartedAt > MAX_TOTAL_PAUSE_MS) {
7769
- warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items`);
8021
+ warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items (pause remains until ${formatLocalDateTime(new Date(this.pausedUntil).toISOString())})`);
7770
8022
  this.flushQueueWithError("Telegram rate limit exceeded max wait time");
7771
- this.pausedUntil = 0;
7772
- this.pauseStartedAt = 0;
7773
8023
  break;
7774
8024
  }
7775
8025
  const waitMs = Math.min(this.pausedUntil - Date.now(), 5e3);
7776
8026
  if (waitMs > 0) await sleep(waitMs);
7777
8027
  }
7778
8028
  if (this.queue.length === 0) break;
8029
+ if (!this.isPaused() && this.pauseStartedAt > 0) {
8030
+ this.pauseStartedAt = 0;
8031
+ }
7779
8032
  this.updateCircuitState();
7780
8033
  const item = this.selectNextItem();
7781
8034
  if (!item) {
@@ -7783,7 +8036,8 @@ var init_telegram_throttle = __esm({
7783
8036
  continue;
7784
8037
  }
7785
8038
  const lastChat = this.lastSendPerChat.get(item.chatId) ?? 0;
7786
- const chatWait = perChatInterval(item.chatId) - (Date.now() - lastChat);
8039
+ const interval = item.priority === 0 /* P0_CRITICAL */ ? P0_PACING_MS : perChatInterval(item.chatId);
8040
+ const chatWait = interval - (Date.now() - lastChat);
7787
8041
  if (chatWait > 0) await sleep(chatWait);
7788
8042
  const globalWait = GLOBAL_INTERVAL_MS - (Date.now() - this.lastGlobalSend);
7789
8043
  if (globalWait > 0) await sleep(globalWait);
@@ -7865,6 +8119,7 @@ var init_telegram_throttle = __esm({
7865
8119
  return await fn();
7866
8120
  } catch (err) {
7867
8121
  if (is429(err)) throw err;
8122
+ if (err instanceof GrammyError && err.error_code >= 400 && err.error_code < 500) throw err;
7868
8123
  if (attempt < MAX_RETRIES2 && err instanceof GrammyError) {
7869
8124
  warn(`[throttle] ${label2} attempt ${attempt + 1}/${MAX_RETRIES2} failed (${err.error_code}), retrying`);
7870
8125
  await sleep(RETRY_DELAY_MS);
@@ -8606,6 +8861,70 @@ function buildHandoffContract() {
8606
8861
  "**Open questions:** Anything unresolved or needing the main agent's attention."
8607
8862
  ].join("\n");
8608
8863
  }
8864
+ function buildAgenticCore() {
8865
+ return `## Task Execution
8866
+
8867
+ - Implement rather than suggest unless the request is clearly read-only.
8868
+ - Read relevant files before answering questions about code or proposing changes. Do not propose changes to code you have not read.
8869
+ - Do not add features, refactor code, or make improvements beyond what was asked. A bug fix does not need surrounding code cleaned up. A simple feature does not need extra configurability.
8870
+ - Do not create helpers, utilities, or abstractions for one-time operations. Do not design for hypothetical future requirements. Three similar lines of code is better than a premature abstraction.
8871
+ - Do not add error handling, fallbacks, or validation for scenarios that cannot happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs).
8872
+ - Do not create files unless absolutely necessary. Prefer editing existing files over creating new ones.
8873
+ - Avoid backwards-compatibility hacks. If something is unused, delete it completely.
8874
+ - Be careful not to introduce security vulnerabilities (injection, XSS, SQL injection, OWASP top 10). If you write insecure code, fix it immediately.
8875
+ - You are highly capable. Defer to user judgement about whether a task is too large to attempt.
8876
+
8877
+ ## Action Safety
8878
+
8879
+ Take local, reversible actions freely (editing files, running tests). For actions that are hard to reverse, affect shared systems, or could be destructive, confirm with the user before proceeding.
8880
+
8881
+ Actions that require confirmation:
8882
+ - Destructive: deleting files/branches, dropping tables, rm -rf, overwriting uncommitted changes
8883
+ - Hard to reverse: force-pushing, git reset --hard, amending published commits, removing dependencies
8884
+ - Visible to others: pushing code, creating/commenting on PRs/issues, sending messages, posting to external services
8885
+
8886
+ When encountering an obstacle, investigate root causes rather than using destructive actions as shortcuts. If you discover unexpected state (unfamiliar files, branches, configuration), investigate before deleting or overwriting \u2014 it may be the user's in-progress work.
8887
+
8888
+ ## Tool Usage
8889
+
8890
+ - Call multiple tools in parallel when they have no dependencies between them. Maximize parallel tool calls for efficiency. Only use sequential calls when outputs feed inputs.
8891
+ - Never use placeholders or guess missing parameters.
8892
+ - For git operations: never amend published commits or force-push without explicit instruction.
8893
+
8894
+ ## Communication
8895
+
8896
+ - Before your first tool call, state in one sentence what you are about to do.
8897
+ - While working, give short updates at key moments: when you find something, change direction, or hit a blocker. One sentence per update is enough.
8898
+ - End-of-turn summary: one or two sentences \u2014 what changed and what is next. Nothing else.
8899
+ - Match responses to the task: a simple question gets a direct answer, not headers and sections.
8900
+ - Do not narrate your internal reasoning, tool selection logic, or thought process. Only output the final answer.
8901
+
8902
+ ## Grounding
8903
+
8904
+ - Never invent library APIs, file paths, CLI commands, or documentation you are not certain exist.
8905
+ - If uncertain, say so. Label inferences clearly: "Based on X, I expect Y" \u2014 not stated as fact.
8906
+ - Verify paths before acting. User paths are often approximate.`;
8907
+ }
8908
+ function buildFollowThrough() {
8909
+ return `PERSISTENCE RULES:
8910
+ - Persist through the full lifecycle: gather context \u2192 plan \u2192 implement \u2192 verify \u2192 report.
8911
+ - Do not stop at analysis, partial fixes, or drafts.
8912
+ - Do not output a plan, status update, or progress summary as your final message.
8913
+ If there is more work to do, do the work. A status message without a following
8914
+ tool call will terminate your turn prematurely.
8915
+ - Only terminate your turn when you are sure the problem is fully solved.
8916
+
8917
+ TOOL PERSISTENCE:
8918
+ - Do not stop early when another tool call would materially improve correctness or completeness.
8919
+ - Keep calling tools until: (1) the task is complete, and (2) verification passes.
8920
+ - If a tool returns empty, errors, or partial results \u2014 retry with a different strategy. Tool errors are not stop conditions.
8921
+
8922
+ BIAS TO ACTION:
8923
+ - Default to implementing with reasonable assumptions rather than asking clarifying questions,
8924
+ unless truly blocked on a detail that would change correctness or safety, or involves an
8925
+ irreversible action.
8926
+ - Never stop after completing only part of the request.`;
8927
+ }
8609
8928
  var REAL_HOME, TASK_WORKER_SKILL_BUDGET;
8610
8929
  var init_shared = __esm({
8611
8930
  "src/bootstrap/templates/shared.ts"() {
@@ -8621,6 +8940,7 @@ var discover_exports = {};
8621
8940
  __export(discover_exports, {
8622
8941
  discoverAllSkills: () => discoverAllSkills,
8623
8942
  invalidateSkillCache: () => invalidateSkillCache,
8943
+ parseFrontmatter: () => parseFrontmatter,
8624
8944
  resolveSkillForTask: () => resolveSkillForTask,
8625
8945
  stripFrontmatter: () => stripFrontmatter
8626
8946
  });
@@ -8693,7 +9013,8 @@ async function scanSkillDir(skillsDir, source) {
8693
9013
  contentHash: hash,
8694
9014
  type: frontmatter.type,
8695
9015
  status: frontmatter.status,
8696
- requires: frontmatter.requires
9016
+ requires: frontmatter.requires,
9017
+ triggers: frontmatter.triggers
8697
9018
  });
8698
9019
  }
8699
9020
  return results;
@@ -8725,7 +9046,8 @@ function mergeAndDeduplicate(raw) {
8725
9046
  contentHash: primary.contentHash,
8726
9047
  type: primary.type,
8727
9048
  status: primary.status,
8728
- requires: primary.requires
9049
+ requires: primary.requires,
9050
+ triggers: primary.triggers
8729
9051
  });
8730
9052
  } else {
8731
9053
  for (const [hash, copies] of byHash) {
@@ -8740,7 +9062,8 @@ function mergeAndDeduplicate(raw) {
8740
9062
  contentHash: hash,
8741
9063
  type: primary.type,
8742
9064
  status: primary.status,
8743
- requires: primary.requires
9065
+ requires: primary.requires,
9066
+ triggers: primary.triggers
8744
9067
  });
8745
9068
  }
8746
9069
  }
@@ -8760,7 +9083,8 @@ function parseFrontmatter(content, fallbackName, source) {
8760
9083
  description: "",
8761
9084
  type: "tool",
8762
9085
  status: source === "cc-claw" ? "imported" : "external",
8763
- requires: []
9086
+ requires: [],
9087
+ triggers: []
8764
9088
  };
8765
9089
  }
8766
9090
  const fm = fmMatch[1];
@@ -8786,12 +9110,21 @@ function parseFrontmatter(content, fallbackName, source) {
8786
9110
  if (cleaned2) requires.push(cleaned2);
8787
9111
  }
8788
9112
  }
9113
+ const triggersMatch = fm.match(/^triggers:\s*\[([^\]]*)\]/m);
9114
+ const triggers = [];
9115
+ if (triggersMatch?.[1]) {
9116
+ for (const item of triggersMatch[1].split(",")) {
9117
+ const cleaned2 = strip(item);
9118
+ if (cleaned2) triggers.push(cleaned2);
9119
+ }
9120
+ }
8789
9121
  return {
8790
9122
  name: nameMatch?.[1] ? strip(nameMatch[1]) : fallbackName,
8791
9123
  description: descMatch?.[1] ? strip(descMatch[1]) : "",
8792
9124
  type,
8793
9125
  status,
8794
- requires
9126
+ requires,
9127
+ triggers
8795
9128
  };
8796
9129
  }
8797
9130
  function stripFrontmatter(content) {
@@ -8802,9 +9135,9 @@ function resolveSkillForTask(skillName) {
8802
9135
  for (const candidate of SKILL_FILE_CANDIDATES3) {
8803
9136
  const skillPath = join9(SKILLS_PATH, skillName, candidate);
8804
9137
  try {
8805
- const { readFileSync: readFileSync35, existsSync: existsSync62 } = __require("fs");
9138
+ const { readFileSync: readFileSync36, existsSync: existsSync62 } = __require("fs");
8806
9139
  if (!existsSync62(skillPath)) continue;
8807
- const raw = readFileSync35(skillPath, "utf-8");
9140
+ const raw = readFileSync36(skillPath, "utf-8");
8808
9141
  const fm = parseFrontmatter(raw, skillName, "cc-claw");
8809
9142
  if (fm.status !== "approved") {
8810
9143
  log(`[skills] Skill "${skillName}" has status "${fm.status}" \u2014 only approved skills can be used as task worker identity`);
@@ -11267,6 +11600,12 @@ ${buildPlatformQuickReference()}
11267
11600
 
11268
11601
  ${buildToolUsageRules()}
11269
11602
 
11603
+ ## Agentic Discipline
11604
+ ${buildAgenticCore()}
11605
+
11606
+ ## Follow-Through
11607
+ ${buildFollowThrough()}
11608
+
11270
11609
  ## CC-Claw Constraints
11271
11610
  ${buildDatabaseSafetyBoundary()}
11272
11611
  - Do not invent library APIs or documentation you are not certain exists.
@@ -11298,28 +11637,13 @@ ${user}
11298
11637
  You are an autonomous agent \u2014 keep going until the user's request is completely
11299
11638
  resolved before ending your turn and yielding back to the user.
11300
11639
 
11301
- PERSISTENCE RULES:
11302
- - Persist through the full lifecycle: gather context \u2192 plan \u2192 implement \u2192 verify
11303
- \u2192 report. Do not stop at analysis, partial fixes, or drafts.
11304
- - Do not output a plan, status update, or progress summary as your final message.
11305
- If there is more work to do, do the work. A status message without a following
11306
- tool call will terminate your turn prematurely.
11307
- - Only terminate your turn when you are sure the problem is fully solved.
11308
-
11309
- TOOL PERSISTENCE:
11310
- - Do not stop early when another tool call would materially improve correctness
11311
- or completeness.
11312
- - Keep calling tools until: (1) the task is complete, and (2) verification passes.
11313
- - If a tool returns empty, errors, or partial results \u2014 retry with a different
11314
- strategy. Tool errors are not stop conditions.
11315
-
11316
- BIAS TO ACTION:
11317
- - Default to implementing with reasonable assumptions rather than asking
11318
- clarifying questions, unless truly blocked on a detail that would change
11319
- correctness or safety, or involves an irreversible action.
11320
- - Never stop after completing only part of the request.
11640
+ ${buildFollowThrough()}
11321
11641
  </default_follow_through_policy>
11322
11642
 
11643
+ <agentic_framework>
11644
+ ${buildAgenticCore()}
11645
+ </agentic_framework>
11646
+
11323
11647
  <output_contract>
11324
11648
  - Simple answers: 1-4 sentences in Telegram HTML, no preamble
11325
11649
  - Code tasks: show the change, explain what changed and why in 1-2 sentences
@@ -11384,18 +11708,15 @@ ${soul}
11384
11708
  ${user}
11385
11709
  </user_profile>
11386
11710
 
11387
- <instructions>
11388
- 1. Implement rather than suggest unless the request is clearly read-only.
11389
- 2. Read relevant files before answering questions about code.
11390
- 3. Keep casual responses concise. Reserve detail for research, code, and complex tasks.
11391
- 4. Confirm before destructive actions (file deletion, force operations, external sends).
11392
- 5. Use cc-claw CLI or daemon HTTP API for state. Run \`cc-claw --ai\` when unsure.
11393
- 6. Verify paths before acting. User paths are often approximate.
11394
- </instructions>
11711
+ <agentic_framework>
11712
+ ${buildAgenticCore()}
11713
+ </agentic_framework>
11714
+
11715
+ <follow_through_policy>
11716
+ ${buildFollowThrough()}
11717
+ </follow_through_policy>
11395
11718
 
11396
11719
  <tool_guidance>
11397
- Call multiple tools in parallel when they have no dependencies.
11398
- Only make changes directly requested. Do not add features or refactor beyond what was asked.
11399
11720
  ${buildToolUsageRules()}
11400
11721
  </tool_guidance>
11401
11722
 
@@ -11423,6 +11744,138 @@ var init_cursor2 = __esm({
11423
11744
  }
11424
11745
  });
11425
11746
 
11747
+ // src/bootstrap/templates/api.ts
11748
+ function buildApiTemplate(soul, user) {
11749
+ const date = getCurrentDate();
11750
+ return `<role>
11751
+ You are the agent described below, operating via Telegram. Current date: ${date}.
11752
+ You are running as a direct API model \u2014 not a CLI tool like Claude Code or Gemini CLI.
11753
+ CC-Claw is your agentic harness: it provides your tools, memory, identity, and orchestration layer.
11754
+ Follow these system instructions precisely. They define how you operate.
11755
+ </role>
11756
+
11757
+ <identity>
11758
+ ${soul}
11759
+ </identity>
11760
+
11761
+ <user_profile>
11762
+ ${user}
11763
+ </user_profile>
11764
+
11765
+ <agentic_framework>
11766
+ ${buildAgenticCore()}
11767
+ </agentic_framework>
11768
+
11769
+ <follow_through_policy>
11770
+ ${buildFollowThrough()}
11771
+ </follow_through_policy>
11772
+
11773
+ <tool_routing>
11774
+ ## API Backend Tool Routing
11775
+
11776
+ You have access to tools provided by CC-Claw via function calling. Use them directly by name.
11777
+
11778
+ External CLI tools (gsearch, pwm, gws, gemcli, nlm, curl, python3, git, npm, cc-claw, etc.)
11779
+ are accessed ONLY via the \`restrictedBash\` tool. NEVER call external CLIs as direct tool
11780
+ names \u2014 they are not registered as native tools and will fail silently.
11781
+
11782
+ Correct: restrictedBash({"command": "gsearch \\"query\\" --type news"})
11783
+ Incorrect: gsearch({"query": "..."}) \u2190 this WILL FAIL
11784
+
11785
+ If a skill or instruction says to use a CLI tool, always route it through restrictedBash.
11786
+ When unsure about available commands, run: restrictedBash({"command": "cc-claw --ai"})
11787
+
11788
+ ${buildCliEnvironment()}
11789
+ </tool_routing>
11790
+
11791
+ <verification_loop>
11792
+ Before finalizing any response, silently check:
11793
+ 1. Is this correct and complete?
11794
+ 2. Is every factual claim grounded in something verified?
11795
+ 3. Does the format match the output contract below?
11796
+ 4. Are there any safety concerns with the actions taken?
11797
+ </verification_loop>
11798
+
11799
+ <safety_boundary>
11800
+ ${buildDatabaseSafetyBoundary()}
11801
+ ${buildToolUsageRules()}
11802
+ </safety_boundary>
11803
+
11804
+ <platform_reference>
11805
+ ${buildSystemCapabilities()}
11806
+ ${buildPlatformQuickReference()}
11807
+ </platform_reference>
11808
+
11809
+ <output_format>
11810
+ Respond in Telegram HTML: <b>bold</b>, <code>inline</code>, <pre>blocks</pre>.
11811
+ No markdown formatting. No flattery openers. No pleasantries.
11812
+ Prose for conversation, structure only when the content requires it.
11813
+ Keep casual responses concise. Reserve detail for research, code, and complex tasks.
11814
+ </output_format>`;
11815
+ }
11816
+ var init_api = __esm({
11817
+ "src/bootstrap/templates/api.ts"() {
11818
+ "use strict";
11819
+ init_shared();
11820
+ }
11821
+ });
11822
+
11823
+ // src/bootstrap/templates/api-task.ts
11824
+ function buildApiTaskTemplate() {
11825
+ const date = getCurrentDate();
11826
+ return `<role>
11827
+ You are a specialist task worker operating via CC-Claw. Current date: ${date}.
11828
+ Your identity and expertise are defined by the skill injected into your prompt.
11829
+ You are a direct API model \u2014 use restrictedBash for all external CLI tools.
11830
+ </role>
11831
+
11832
+ <follow_through_policy>
11833
+ ${buildFollowThrough()}
11834
+ </follow_through_policy>
11835
+
11836
+ <operating_rules>
11837
+ ${buildTaskWorkerRules()}
11838
+ </operating_rules>
11839
+
11840
+ <tool_routing>
11841
+ External CLI tools are accessed ONLY via the restrictedBash tool.
11842
+ NEVER call external CLIs as direct tool names \u2014 they will fail silently.
11843
+ Correct: restrictedBash({"command": "command here"})
11844
+ Incorrect: command({"arg": "..."}) \u2190 this WILL FAIL
11845
+ </tool_routing>
11846
+
11847
+ <cli_environment>
11848
+ ${buildCliEnvironment()}
11849
+ </cli_environment>
11850
+
11851
+ <verification_loop>
11852
+ Before finalizing any response, silently check:
11853
+ 1. Is this correct and complete?
11854
+ 2. Is every factual claim grounded in something verified?
11855
+ 3. Does the output match the task requirements?
11856
+ </verification_loop>
11857
+
11858
+ <grounding_rules>
11859
+ Never invent library APIs, file paths, or documentation. If uncertain, say so.
11860
+ Verify paths before acting. User paths are often approximate.
11861
+ </grounding_rules>
11862
+
11863
+ <safety_boundary>
11864
+ Confirm before: deleting files, posting to external services, database schema changes.
11865
+ Do not access ~/.cc-claw/data/cc-claw.db directly.
11866
+ </safety_boundary>
11867
+
11868
+ <output_contract>
11869
+ ${buildHandoffContract()}
11870
+ </output_contract>`;
11871
+ }
11872
+ var init_api_task = __esm({
11873
+ "src/bootstrap/templates/api-task.ts"() {
11874
+ "use strict";
11875
+ init_shared();
11876
+ }
11877
+ });
11878
+
11426
11879
  // src/bootstrap/templates/claude-task.ts
11427
11880
  function buildClaudeTaskTemplate() {
11428
11881
  const date = getCurrentDate();
@@ -11481,26 +11934,7 @@ Your identity and expertise are defined by the skill injected into your prompt.
11481
11934
  You are an autonomous agent \u2014 keep going until the task is completely resolved
11482
11935
  before ending your turn and yielding back to the user.
11483
11936
 
11484
- PERSISTENCE RULES:
11485
- - Persist through the full lifecycle: gather context \u2192 plan \u2192 implement \u2192 verify
11486
- \u2192 report. Do not stop at analysis, partial fixes, or drafts.
11487
- - Do not output a plan, status update, or progress summary as your final message.
11488
- If there is more work to do, do the work. A status message without a following
11489
- tool call will terminate your turn prematurely.
11490
- - Only terminate your turn when you are sure the task is fully solved.
11491
-
11492
- TOOL PERSISTENCE:
11493
- - Do not stop early when another tool call would materially improve correctness
11494
- or completeness.
11495
- - Keep calling tools until: (1) the task is complete, and (2) verification passes.
11496
- - If a tool returns empty, errors, or partial results \u2014 retry with a different
11497
- strategy. Tool errors are not stop conditions.
11498
-
11499
- BIAS TO ACTION:
11500
- - Default to implementing with reasonable assumptions rather than asking
11501
- clarifying questions, unless truly blocked on a detail that would change
11502
- correctness or safety.
11503
- - Never stop after completing only part of the task.
11937
+ ${buildFollowThrough()}
11504
11938
  </default_follow_through_policy>
11505
11939
 
11506
11940
  <operating_rules>
@@ -11664,6 +12098,10 @@ function syncNativeCliFiles() {
11664
12098
  writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-codex.md"), codexContent, "utf-8");
11665
12099
  writeFileSync6(join14(WORKSPACE_PATH, "AGENTS.md"), codexContent, "utf-8");
11666
12100
  writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-cursor.md"), cursorContent, "utf-8");
12101
+ const apiContent = buildApiTemplate(soul, user);
12102
+ writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-api.md"), apiContent, "utf-8");
12103
+ const apiTaskContent = buildApiTaskTemplate();
12104
+ writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-api-task.md"), apiTaskContent, "utf-8");
11667
12105
  const fallbackContent = [
11668
12106
  "# CC-Claw System Instructions",
11669
12107
  "",
@@ -11686,7 +12124,7 @@ function syncNativeCliFiles() {
11686
12124
  const codexTaskContent = buildCodexTaskTemplate();
11687
12125
  writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-claude-task.md"), claudeTaskContent, "utf-8");
11688
12126
  writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-codex-task.md"), codexTaskContent, "utf-8");
11689
- log("[bootstrap] Synced identity files: CC-CLAW-claude.md, GEMINI.md, CC-CLAW-codex.md, CC-CLAW-cursor.md, CC-CLAW.md (fallback), CC-CLAW-claude-task.md, CC-CLAW-codex-task.md");
12127
+ log("[bootstrap] Synced identity files: CC-CLAW-claude.md, GEMINI.md, CC-CLAW-codex.md, CC-CLAW-cursor.md, CC-CLAW-api.md, CC-CLAW-api-task.md, CC-CLAW.md (fallback), CC-CLAW-claude-task.md, CC-CLAW-codex-task.md");
11690
12128
  }
11691
12129
  var SOUL_PATH, USER_PATH, CONTEXT_DIR, LEGACY_SOUL_PATH, LEGACY_USER_PATH, LEGACY_CLAUDE_MD, LEGACY_GEMINI_MD;
11692
12130
  var init_init = __esm({
@@ -11698,6 +12136,8 @@ var init_init = __esm({
11698
12136
  init_gemini2();
11699
12137
  init_codex2();
11700
12138
  init_cursor2();
12139
+ init_api();
12140
+ init_api_task();
11701
12141
  init_claude_task();
11702
12142
  init_codex_task();
11703
12143
  init_shared();
@@ -11712,19 +12152,333 @@ var init_init = __esm({
11712
12152
  }
11713
12153
  });
11714
12154
 
11715
- // src/bootstrap/loader.ts
11716
- import { readFileSync as readFileSync10, readdirSync as readdirSync6 } from "fs";
11717
- import { join as join15 } from "path";
11718
- function tokenize(text) {
11719
- return new Set(
11720
- text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 3)
11721
- );
12155
+ // src/intent/complexity.ts
12156
+ var complexity_exports = {};
12157
+ __export(complexity_exports, {
12158
+ classifyComplexity: () => classifyComplexity
12159
+ });
12160
+ function wordCount(text) {
12161
+ return text.trim().split(/\s+/).filter(Boolean).length;
12162
+ }
12163
+ function classifyComplexity(text) {
12164
+ const trimmed = text.trim();
12165
+ const lower = trimmed.toLowerCase();
12166
+ const words = wordCount(trimmed);
12167
+ if (TRIVIAL_EXACT.has(lower)) {
12168
+ log(`[complexity] "${lower}" -> trivial (exact match)`);
12169
+ return "trivial";
12170
+ }
12171
+ if (trimmed.length <= 4 && /^[\p{Emoji}\s]+$/u.test(trimmed)) {
12172
+ log(`[complexity] "${trimmed}" -> trivial (emoji-only)`);
12173
+ return "trivial";
12174
+ }
12175
+ if (COMPLEX_SIGNALS.test(trimmed) && (words > 15 || trimmed.length > 200)) {
12176
+ log(`[complexity] "${trimmed.slice(0, 40)}..." -> complex (signal words + length)`);
12177
+ return "complex";
12178
+ }
12179
+ if (MUTATION_VERBS.test(trimmed)) {
12180
+ const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
12181
+ log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (mutation verb)`);
12182
+ return tier;
12183
+ }
12184
+ for (const pattern of CODE_PATTERNS) {
12185
+ if (pattern.test(trimmed)) {
12186
+ const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
12187
+ log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (code pattern)`);
12188
+ return tier;
12189
+ }
12190
+ }
12191
+ if (trimmed.length > 200 || words > 25) {
12192
+ const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
12193
+ log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (long message)`);
12194
+ return tier;
12195
+ }
12196
+ for (const pattern of QUESTION_PATTERNS) {
12197
+ if (pattern.test(trimmed)) {
12198
+ log(`[complexity] "${trimmed.slice(0, 40)}..." -> simple (question)`);
12199
+ return "simple";
12200
+ }
12201
+ }
12202
+ log(`[complexity] "${trimmed.slice(0, 40)}..." -> moderate (default)`);
12203
+ return "moderate";
12204
+ }
12205
+ var TRIVIAL_EXACT, COMPLEX_SIGNALS, MUTATION_VERBS, CODE_PATTERNS, QUESTION_PATTERNS;
12206
+ var init_complexity = __esm({
12207
+ "src/intent/complexity.ts"() {
12208
+ "use strict";
12209
+ init_log();
12210
+ TRIVIAL_EXACT = /* @__PURE__ */ new Set([
12211
+ "hey",
12212
+ "hi",
12213
+ "hello",
12214
+ "yo",
12215
+ "sup",
12216
+ "howdy",
12217
+ "hiya",
12218
+ "thanks",
12219
+ "thank you",
12220
+ "thx",
12221
+ "ty",
12222
+ "thank u",
12223
+ "ok",
12224
+ "okay",
12225
+ "k",
12226
+ "kk",
12227
+ "cool",
12228
+ "nice",
12229
+ "great",
12230
+ "awesome",
12231
+ "perfect",
12232
+ "good morning",
12233
+ "good night",
12234
+ "good evening",
12235
+ "gm",
12236
+ "gn",
12237
+ "bye",
12238
+ "goodbye",
12239
+ "later",
12240
+ "see ya",
12241
+ "cya",
12242
+ "lol",
12243
+ "lmao",
12244
+ "haha",
12245
+ "heh",
12246
+ "np",
12247
+ "no problem",
12248
+ "no worries",
12249
+ "nw",
12250
+ "got it",
12251
+ "understood",
12252
+ "roger",
12253
+ "copy",
12254
+ "good",
12255
+ "fine",
12256
+ "alright",
12257
+ "sure",
12258
+ "yes",
12259
+ "no",
12260
+ "yep",
12261
+ "nope",
12262
+ "yeah",
12263
+ "nah"
12264
+ ]);
12265
+ COMPLEX_SIGNALS = /\b(architect|design|research|plan|strategy|migration|microservice|distributed|end-to-end|trade-offs|pros\s+and\s+cons)\b/i;
12266
+ MUTATION_VERBS = /\b(fix|create|build|deploy|refactor|implement|write|add|update|edit|test|debug)\b/i;
12267
+ CODE_PATTERNS = [
12268
+ /```/,
12269
+ // code blocks
12270
+ /\.[a-z]{1,5}\b/,
12271
+ // file extensions (.ts, .py, .json)
12272
+ /[/\\][\w.-]+/
12273
+ // file paths (/src/foo, .\bar)
12274
+ ];
12275
+ QUESTION_PATTERNS = [
12276
+ /^(?:what|which|where|when|why|how)\b/i,
12277
+ /^(?:show|tell|list|explain)\b/i,
12278
+ /^(?:is|are)\b/i
12279
+ ];
12280
+ }
12281
+ });
12282
+
12283
+ // src/skills/inject.ts
12284
+ import { readFileSync as readFileSync10 } from "fs";
12285
+ function setInjectedSkills(chatId, skillNames) {
12286
+ injectedSkillsMap.set(chatId, skillNames);
12287
+ }
12288
+ function getAndClearInjectedSkills(chatId) {
12289
+ const skills2 = injectedSkillsMap.get(chatId) ?? [];
12290
+ injectedSkillsMap.delete(chatId);
12291
+ return skills2;
11722
12292
  }
11723
- function stripFrontmatter3(content) {
11724
- if (!content.startsWith("---")) return content;
11725
- const end = content.indexOf("---", 3);
11726
- return end === -1 ? content : content.slice(end + 3).trim();
12293
+ function shouldSkipInjection(userMessage, profile, userExplicitNative) {
12294
+ if (userExplicitNative) return true;
12295
+ if (profile === "minimal") return true;
12296
+ const complexity = classifyComplexity(userMessage);
12297
+ return SKIP_COMPLEXITY.has(complexity);
12298
+ }
12299
+ function scoreSkills(userMessage, skills2) {
12300
+ const msgWords = tokenize(userMessage);
12301
+ const msgWordsAll = tokenizeAll(userMessage);
12302
+ if (msgWordsAll.size === 0) return [];
12303
+ const results = [];
12304
+ for (const skill of skills2) {
12305
+ if (skill.status !== "approved" && skill.status !== "imported") continue;
12306
+ let score = 0;
12307
+ const triggerWords = new Set(skill.triggers.map((t) => t.toLowerCase()));
12308
+ for (const word of msgWordsAll) {
12309
+ if (triggerWords.has(word)) score += 2;
12310
+ }
12311
+ const metaWords = tokenize([skill.name, skill.description].join(" "));
12312
+ for (const word of msgWords) {
12313
+ if (metaWords.has(word) && !STOPWORDS.has(word)) score++;
12314
+ }
12315
+ if (score >= MIN_SCORE) {
12316
+ results.push({ name: skill.name, score, filePath: skill.filePath, requires: skill.requires });
12317
+ }
12318
+ }
12319
+ return results.sort((a, b) => b.score - a.score);
12320
+ }
12321
+ function formatSkillBlock(skills2, budgetChars = TOTAL_BUDGET_CHARS) {
12322
+ if (skills2.length === 0) return null;
12323
+ const lines = ["[Skill context]"];
12324
+ let totalChars = 0;
12325
+ for (const skill of skills2) {
12326
+ let content = skill.content;
12327
+ const remaining = budgetChars - totalChars;
12328
+ if (remaining <= 0) break;
12329
+ if (content.length > remaining) {
12330
+ content = content.slice(0, remaining) + "\n\u2026(truncated)";
12331
+ }
12332
+ lines.push(`## ${skill.name}`);
12333
+ lines.push(content);
12334
+ lines.push("");
12335
+ totalChars += content.length;
12336
+ }
12337
+ lines.push("[End skill context]");
12338
+ return lines.join("\n");
11727
12339
  }
12340
+ function loadWithDeps(skill, allSkills, usedNames, entries, charsSoFar, charCap) {
12341
+ const content = readSkillContent(skill.filePath);
12342
+ if (!content) return charsSoFar;
12343
+ if (charsSoFar + content.length > charCap) return charsSoFar;
12344
+ entries.push({ name: skill.name, content });
12345
+ usedNames.add(skill.name);
12346
+ let chars = charsSoFar + content.length;
12347
+ for (const dep of skill.requires) {
12348
+ if (usedNames.has(dep)) continue;
12349
+ const depSkill = allSkills.find((s) => s.name === dep);
12350
+ if (!depSkill) continue;
12351
+ const depContent = readSkillContent(depSkill.filePath);
12352
+ if (!depContent) continue;
12353
+ if (chars + depContent.length > charCap) continue;
12354
+ entries.push({ name: depSkill.name, content: depContent });
12355
+ usedNames.add(depSkill.name);
12356
+ chars += depContent.length;
12357
+ }
12358
+ return chars;
12359
+ }
12360
+ function injectSkillContext(userMessage, allSkills, pinnedSkillNames, profile, userExplicitNative, chatId) {
12361
+ const hasPins = pinnedSkillNames.length > 0;
12362
+ const skipAuto = shouldSkipInjection(userMessage, profile, userExplicitNative);
12363
+ if (!hasPins && skipAuto) return null;
12364
+ const entries = [];
12365
+ const usedNames = /* @__PURE__ */ new Set();
12366
+ let pinnedChars = 0;
12367
+ for (const name of pinnedSkillNames) {
12368
+ if (usedNames.has(name)) continue;
12369
+ const skill = allSkills.find((s) => s.name === name);
12370
+ if (!skill) continue;
12371
+ pinnedChars = loadWithDeps(skill, allSkills, usedNames, entries, pinnedChars, PINNED_BUDGET_CHARS);
12372
+ }
12373
+ if (!skipAuto) {
12374
+ const scored = scoreSkills(userMessage, allSkills);
12375
+ let autoChars = 0;
12376
+ for (const match of scored) {
12377
+ if (usedNames.has(match.name)) continue;
12378
+ const skill = allSkills.find((s) => s.name === match.name);
12379
+ if (!skill) continue;
12380
+ autoChars = loadWithDeps(skill, allSkills, usedNames, entries, autoChars, AUTO_BUDGET_CHARS);
12381
+ }
12382
+ }
12383
+ if (entries.length === 0) return null;
12384
+ const injectedNames = entries.map((e) => e.name);
12385
+ if (chatId) {
12386
+ setInjectedSkills(chatId, injectedNames);
12387
+ }
12388
+ log(`[skills] Injected ${injectedNames.length} skill(s): ${injectedNames.join(", ")}`);
12389
+ return formatSkillBlock(entries);
12390
+ }
12391
+ function readSkillContent(filePath) {
12392
+ try {
12393
+ const raw = readFileSync10(filePath, "utf-8");
12394
+ return stripFrontmatter(raw);
12395
+ } catch {
12396
+ return null;
12397
+ }
12398
+ }
12399
+ var AUTO_BUDGET_CHARS, PINNED_BUDGET_CHARS, TOTAL_BUDGET_CHARS, MIN_SCORE, injectedSkillsMap, SKIP_COMPLEXITY, STOPWORDS, tokenize, tokenizeAll;
12400
+ var init_inject2 = __esm({
12401
+ "src/skills/inject.ts"() {
12402
+ "use strict";
12403
+ init_complexity();
12404
+ init_discover();
12405
+ init_text_utils();
12406
+ init_log();
12407
+ AUTO_BUDGET_CHARS = 3e3;
12408
+ PINNED_BUDGET_CHARS = 5e3;
12409
+ TOTAL_BUDGET_CHARS = 8e3;
12410
+ MIN_SCORE = 2;
12411
+ injectedSkillsMap = /* @__PURE__ */ new Map();
12412
+ SKIP_COMPLEXITY = /* @__PURE__ */ new Set(["trivial", "simple"]);
12413
+ STOPWORDS = /* @__PURE__ */ new Set([
12414
+ "about",
12415
+ "after",
12416
+ "also",
12417
+ "been",
12418
+ "before",
12419
+ "being",
12420
+ "between",
12421
+ "both",
12422
+ "could",
12423
+ "does",
12424
+ "doing",
12425
+ "done",
12426
+ "down",
12427
+ "during",
12428
+ "each",
12429
+ "even",
12430
+ "every",
12431
+ "from",
12432
+ "have",
12433
+ "having",
12434
+ "help",
12435
+ "here",
12436
+ "into",
12437
+ "just",
12438
+ "know",
12439
+ "like",
12440
+ "make",
12441
+ "more",
12442
+ "most",
12443
+ "much",
12444
+ "need",
12445
+ "only",
12446
+ "other",
12447
+ "over",
12448
+ "some",
12449
+ "such",
12450
+ "than",
12451
+ "that",
12452
+ "their",
12453
+ "them",
12454
+ "then",
12455
+ "there",
12456
+ "these",
12457
+ "they",
12458
+ "this",
12459
+ "through",
12460
+ "very",
12461
+ "want",
12462
+ "well",
12463
+ "were",
12464
+ "what",
12465
+ "when",
12466
+ "where",
12467
+ "which",
12468
+ "while",
12469
+ "will",
12470
+ "with",
12471
+ "would",
12472
+ "your"
12473
+ ]);
12474
+ tokenize = (text) => tokenizeText(text, 4);
12475
+ tokenizeAll = (text) => tokenizeText(text, 1);
12476
+ }
12477
+ });
12478
+
12479
+ // src/bootstrap/loader.ts
12480
+ import { readFileSync as readFileSync11, readdirSync as readdirSync6 } from "fs";
12481
+ import { join as join15 } from "path";
11728
12482
  function loadMarkdownDir(dir, cacheRef, wordSource = (c) => c) {
11729
12483
  if (cacheRef.v && Date.now() - cacheRef.v.timestamp < CACHE_TTL_MS4) {
11730
12484
  return cacheRef.v.files;
@@ -11734,8 +12488,8 @@ function loadMarkdownDir(dir, cacheRef, wordSource = (c) => c) {
11734
12488
  for (const entry of readdirSync6(dir)) {
11735
12489
  if (!entry.endsWith(".md")) continue;
11736
12490
  try {
11737
- const content = readFileSync10(join15(dir, entry), "utf-8").trim();
11738
- if (content) files.set(entry, { content, words: tokenize(wordSource(content)) });
12491
+ const content = readFileSync11(join15(dir, entry), "utf-8").trim();
12492
+ if (content) files.set(entry, { content, words: tokenize2(wordSource(content)) });
11739
12493
  } catch {
11740
12494
  continue;
11741
12495
  }
@@ -11746,7 +12500,7 @@ function loadMarkdownDir(dir, cacheRef, wordSource = (c) => c) {
11746
12500
  return files;
11747
12501
  }
11748
12502
  function findBestMatch(userMessage, files, opts) {
11749
- const msgWords = tokenize(userMessage);
12503
+ const msgWords = tokenize2(userMessage);
11750
12504
  if (msgWords.size === 0) return null;
11751
12505
  let best = null;
11752
12506
  for (const entry of files.values()) {
@@ -11769,16 +12523,6 @@ function searchContext(userMessage) {
11769
12523
  { maxChars: MAX_CONTEXT_CHARS }
11770
12524
  );
11771
12525
  }
11772
- function searchSkill(userMessage) {
11773
- return findBestMatch(
11774
- userMessage,
11775
- loadMarkdownDir(SKILLS_DIR, skillCacheRef, stripFrontmatter3),
11776
- {
11777
- maxChars: MAX_SKILL_CHARS,
11778
- filter: ({ content }) => content.includes("status: approved") || content.includes("status: imported")
11779
- }
11780
- );
11781
- }
11782
12526
  async function assembleBootstrapPrompt(userMessage, entityType = "main", profile = "interactive", chatId, permMode, responseStyle, agentMode, sideQuestContext, planningDirective, chatContext, backendType) {
11783
12527
  const sections = [];
11784
12528
  if (planningDirective) {
@@ -11792,15 +12536,24 @@ async function assembleBootstrapPrompt(userMessage, entityType = "main", profile
11792
12536
  sections.push(buildPermissionNotice(permMode));
11793
12537
  }
11794
12538
  if (backendType === "api") {
11795
- sections.push(`[API Backend \u2014 Tool Usage]
12539
+ if (!apiHarnessCache.main || Date.now() - apiHarnessCache.cachedMs > 5e3) {
12540
+ try {
12541
+ apiHarnessCache.main = readFileSync11(join15(IDENTITY_PATH, "CC-CLAW-api.md"), "utf-8");
12542
+ apiHarnessCache.task = readFileSync11(join15(IDENTITY_PATH, "CC-CLAW-api-task.md"), "utf-8");
12543
+ apiHarnessCache.cachedMs = Date.now();
12544
+ } catch {
12545
+ }
12546
+ }
12547
+ const harness = entityType === "task" ? apiHarnessCache.task : apiHarnessCache.main;
12548
+ if (harness) {
12549
+ sections.push(harness);
12550
+ } else {
12551
+ warn("[bootstrap] API harness files not yet generated, using fallback");
12552
+ sections.push(`[API Backend \u2014 Tool Usage]
11796
12553
  You are operating as a direct API model (not a CLI like Claude Code or Gemini CLI).
11797
- External tools (gsearch, pwm, gws, gemcli, nlm, curl, python3, etc.) are accessed ONLY via the \`restrictedBash\` tool.
11798
- NEVER call external CLIs as direct tools by name \u2014 they are not registered as native tools.
11799
- Correct: restrictedBash({"command": "gsearch \\"query\\" --type news"})
11800
- Incorrect: gsearch({"query": "..."}) \u2190 this will fail silently
11801
- If a skill or instruction says to use a CLI tool, always route it through restrictedBash.
11802
-
12554
+ External tools are accessed ONLY via the \`restrictedBash\` tool. NEVER call external CLIs as direct tool names.
11803
12555
  ${buildCliEnvironment()}`);
12556
+ }
11804
12557
  }
11805
12558
  if (responseStyle) {
11806
12559
  if (responseStyle === "concise") {
@@ -11830,10 +12583,23 @@ ${ctx}`);
11830
12583
  }
11831
12584
  }
11832
12585
  if (profile === "interactive" || profile === "chat") {
11833
- const skill = searchSkill(userMessage);
11834
- if (skill) {
11835
- sections.push(`[Relevant skill]
11836
- ${skill}`);
12586
+ const pinnedSkills = chatId ? getPinnedSkills(chatId) : [];
12587
+ const userExplicitNative = agentMode === "native" && !sideQuestContext;
12588
+ try {
12589
+ const allSkills = await discoverAllSkills();
12590
+ const skillBlock = injectSkillContext(
12591
+ userMessage,
12592
+ allSkills,
12593
+ pinnedSkills,
12594
+ profile,
12595
+ userExplicitNative,
12596
+ chatId
12597
+ );
12598
+ if (skillBlock) {
12599
+ sections.push(skillBlock);
12600
+ }
12601
+ } catch (err) {
12602
+ warn(`[bootstrap] Skill injection failed: ${err instanceof Error ? err.message : err}`);
11837
12603
  }
11838
12604
  }
11839
12605
  if (chatId && profile !== "minimal" && profile !== "chat") {
@@ -12001,7 +12767,7 @@ ${parts.join("\n")}${MEMORY_DISCIPLINE}`;
12001
12767
  }
12002
12768
  return null;
12003
12769
  }
12004
- var lastSyncMs, CONTEXT_DIR2, SKILLS_DIR, MAX_CONTEXT_CHARS, MAX_SKILL_CHARS, CACHE_TTL_MS4, contextCacheRef, skillCacheRef, ACTIVITY_TOKEN_BUDGET, INBOX_TOKEN_BUDGET, WHITEBOARD_TOKEN_BUDGET;
12770
+ var lastSyncMs, apiHarnessCache, CONTEXT_DIR2, SKILLS_DIR, MAX_CONTEXT_CHARS, CACHE_TTL_MS4, tokenize2, contextCacheRef, ACTIVITY_TOKEN_BUDGET, INBOX_TOKEN_BUDGET, WHITEBOARD_TOKEN_BUDGET;
12005
12771
  var init_loader2 = __esm({
12006
12772
  "src/bootstrap/loader.ts"() {
12007
12773
  "use strict";
@@ -12014,14 +12780,22 @@ var init_loader2 = __esm({
12014
12780
  init_store5();
12015
12781
  init_store();
12016
12782
  init_backends();
12783
+ init_inject2();
12784
+ init_discover();
12785
+ init_chat_settings();
12786
+ init_text_utils();
12017
12787
  lastSyncMs = 0;
12788
+ apiHarnessCache = {
12789
+ main: null,
12790
+ task: null,
12791
+ cachedMs: 0
12792
+ };
12018
12793
  CONTEXT_DIR2 = join15(WORKSPACE_PATH, "context");
12019
12794
  SKILLS_DIR = join15(WORKSPACE_PATH, "skills");
12020
12795
  MAX_CONTEXT_CHARS = 4e3;
12021
- MAX_SKILL_CHARS = 6e3;
12022
12796
  CACHE_TTL_MS4 = 3e4;
12797
+ tokenize2 = (text) => tokenizeText(text, 4);
12023
12798
  contextCacheRef = { v: null };
12024
- skillCacheRef = { v: null };
12025
12799
  ACTIVITY_TOKEN_BUDGET = 1500;
12026
12800
  INBOX_TOKEN_BUDGET = 2e3;
12027
12801
  WHITEBOARD_TOKEN_BUDGET = 500;
@@ -12056,7 +12830,8 @@ __export(client_exports, {
12056
12830
  listModels: () => listModels,
12057
12831
  ping: () => ping,
12058
12832
  runningModels: () => runningModels,
12059
- showModel: () => showModel
12833
+ showModel: () => showModel,
12834
+ unloadModel: () => unloadModel
12060
12835
  });
12061
12836
  function buildHeaders(apiKey) {
12062
12837
  const headers = {
@@ -12142,6 +12917,30 @@ async function deleteModel(baseUrl, modelName, opts) {
12142
12917
  clearTimeout(timeout);
12143
12918
  }
12144
12919
  }
12920
+ async function unloadModel(baseUrl, modelName, opts) {
12921
+ const controller = new AbortController();
12922
+ const timeout = setTimeout(
12923
+ () => controller.abort(),
12924
+ opts?.timeoutMs ?? 5e3
12925
+ // short timeout — unload is best-effort
12926
+ );
12927
+ try {
12928
+ const res = await fetch(`${baseUrl}/api/generate`, {
12929
+ method: "POST",
12930
+ headers: buildHeaders(opts?.apiKey),
12931
+ body: JSON.stringify({ model: modelName, keep_alive: 0 }),
12932
+ signal: controller.signal
12933
+ });
12934
+ if (!res.ok) {
12935
+ const body = await res.text().catch(() => "");
12936
+ throw new Error(`Ollama unload failed (${res.status}): ${body}`);
12937
+ }
12938
+ await res.text().catch(() => {
12939
+ });
12940
+ } finally {
12941
+ clearTimeout(timeout);
12942
+ }
12943
+ }
12145
12944
  async function listModels(baseUrl, opts) {
12146
12945
  const res = await fetch(`${baseUrl}/api/tags`, {
12147
12946
  signal: opts?.signal,
@@ -12829,7 +13628,9 @@ __export(service_exports, {
12829
13628
  isAnyServerOnline: () => isAnyServerOnline,
12830
13629
  listServers: () => listServers2,
12831
13630
  removeServer: () => removeServer2,
12832
- runQualityGateTest: () => runQualityGateTest
13631
+ runQualityGateTest: () => runQualityGateTest,
13632
+ tryUnloadModel: () => tryUnloadModel,
13633
+ unloadModel: () => unloadModel2
12833
13634
  });
12834
13635
  function addServer2(name, host, port = 11434, apiKey) {
12835
13636
  const existing = getServer(name);
@@ -13019,6 +13820,17 @@ function getServerForModel(modelName) {
13019
13820
  apiKey: server.apiKey
13020
13821
  };
13021
13822
  }
13823
+ async function unloadModel2(modelName) {
13824
+ const serverInfo = getServerForModel(modelName);
13825
+ if (!serverInfo) return;
13826
+ await unloadModel(serverInfo.baseUrl, modelName, { apiKey: serverInfo.apiKey });
13827
+ log(`[ollama] Unloaded model: ${modelName}`);
13828
+ }
13829
+ function tryUnloadModel(modelName) {
13830
+ unloadModel2(modelName).catch((err) => {
13831
+ warn(`[ollama] Model unload failed for ${modelName}: ${err}`);
13832
+ });
13833
+ }
13022
13834
  function extractContextWindow(show) {
13023
13835
  const info = show.model_info ?? {};
13024
13836
  for (const key of Object.keys(info)) {
@@ -13382,7 +14194,7 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
13382
14194
  const cap = getOllamaTranscriptCap(ollamaModel);
13383
14195
  const key = `${ollamaAdapter.id}:${ollamaModel}`;
13384
14196
  tried.add(key);
13385
- const directFn = (prompt) => ollamaAdapter.streamDirect(prompt, ollamaModel);
14197
+ const directFn = (prompt) => ollamaAdapter.streamDirect(prompt, ollamaModel, { thinkingLevel: "off" });
13386
14198
  const result = await attemptSummarizeDirect(chatId, directFn, "ollama", ollamaModel, entries, cap);
13387
14199
  if (result.success) {
13388
14200
  await extractAndLogSignals(result.rawText, chatId, "ollama", ollamaModel);
@@ -13400,7 +14212,7 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
13400
14212
  const key = `${targetAdapter.id}:${model2}`;
13401
14213
  if (!tried.has(key)) {
13402
14214
  tried.add(key);
13403
- const result = targetAdapter.streamDirect ? await attemptSummarizeDirect(chatId, (p) => targetAdapter.streamDirect(p, model2), targetAdapter.id, model2, entries, getTranscriptCap(model2)) : await attemptSummarize(chatId, targetAdapter, model2, entries);
14215
+ const result = targetAdapter.streamDirect ? await attemptSummarizeDirect(chatId, (p) => targetAdapter.streamDirect(p, model2, { thinkingLevel: "off" }), targetAdapter.id, model2, entries, getTranscriptCap(model2)) : await attemptSummarize(chatId, targetAdapter, model2, entries);
13404
14216
  if (result.success) {
13405
14217
  await extractAndLogSignals(result.rawText, chatId, targetAdapter.id, model2);
13406
14218
  if (clearLogAfter) clearLog(chatId);
@@ -13418,7 +14230,7 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
13418
14230
  const key = `${adapter.id}:${model2}`;
13419
14231
  if (!tried.has(key)) {
13420
14232
  tried.add(key);
13421
- const result = adapter.streamDirect ? await attemptSummarizeDirect(chatId, (p) => adapter.streamDirect(p, model2), adapter.id, model2, entries, adapter.id === "ollama" ? getOllamaTranscriptCap(model2) : getTranscriptCap(model2)) : await attemptSummarize(chatId, adapter, model2, entries);
14233
+ const result = adapter.streamDirect ? await attemptSummarizeDirect(chatId, (p) => adapter.streamDirect(p, model2, { thinkingLevel: "off" }), adapter.id, model2, entries, adapter.id === "ollama" ? getOllamaTranscriptCap(model2) : getTranscriptCap(model2)) : await attemptSummarize(chatId, adapter, model2, entries);
13422
14234
  if (result.success) {
13423
14235
  await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
13424
14236
  if (clearLogAfter) clearLog(chatId);
@@ -13440,7 +14252,7 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
13440
14252
  let result;
13441
14253
  if (adapter.streamDirect) {
13442
14254
  const cap = adapter.id === "ollama" ? getOllamaTranscriptCap(model2) : getTranscriptCap(model2);
13443
- const directFn = (prompt) => adapter.streamDirect(prompt, model2);
14255
+ const directFn = (prompt) => adapter.streamDirect(prompt, model2, { thinkingLevel: "off" });
13444
14256
  result = await attemptSummarizeDirect(chatId, directFn, adapter.id, model2, entries, cap);
13445
14257
  } else {
13446
14258
  result = await attemptSummarize(chatId, adapter, model2, entries);
@@ -13817,6 +14629,7 @@ function classifyReaction(emoji) {
13817
14629
  return null;
13818
14630
  }
13819
14631
  async function detectAndLogSignals(chatId, userMessage, agentResponse, ctx = {}) {
14632
+ const injectedSkills = getAndClearInjectedSkills(chatId);
13820
14633
  const db3 = getDb();
13821
14634
  const status = getReflectionStatus(db3, chatId);
13822
14635
  if (status === "frozen") return;
@@ -13824,18 +14637,29 @@ async function detectAndLogSignals(chatId, userMessage, agentResponse, ctx = {})
13824
14637
  if (recentCount >= RATE_LIMIT_MAX_SIGNALS) return;
13825
14638
  const signals = classifySignals(userMessage, agentResponse);
13826
14639
  const context = agentResponse.slice(0, 500) || null;
14640
+ const uniqueByType = /* @__PURE__ */ new Map();
13827
14641
  for (const signal of signals) {
13828
- logSignal(db3, {
13829
- chatId,
13830
- signalType: signal.signalType,
13831
- trigger: signal.trigger,
13832
- context,
13833
- source: signal.source,
13834
- confidence: signal.confidence,
13835
- backend: ctx.backendId ?? null,
13836
- model: ctx.model ?? null,
13837
- thinkingLevel: ctx.thinkingLevel ?? null
13838
- });
14642
+ const existing = uniqueByType.get(signal.signalType);
14643
+ if (!existing || signal.confidence > existing.confidence) {
14644
+ uniqueByType.set(signal.signalType, signal);
14645
+ }
14646
+ }
14647
+ const skillList = injectedSkills.length > 0 ? injectedSkills : [void 0];
14648
+ for (const signal of uniqueByType.values()) {
14649
+ for (const skillName of skillList) {
14650
+ logSignal(db3, {
14651
+ chatId,
14652
+ signalType: signal.signalType,
14653
+ trigger: signal.trigger,
14654
+ context,
14655
+ source: signal.source,
14656
+ confidence: signal.confidence,
14657
+ backend: ctx.backendId ?? null,
14658
+ model: ctx.model ?? null,
14659
+ thinkingLevel: ctx.thinkingLevel ?? null,
14660
+ skillName
14661
+ });
14662
+ }
13839
14663
  }
13840
14664
  }
13841
14665
  function logReactionSignal(chatId, emoji) {
@@ -13878,6 +14702,7 @@ var init_detect = __esm({
13878
14702
  "use strict";
13879
14703
  init_store4();
13880
14704
  init_store5();
14705
+ init_inject2();
13881
14706
  CORRECTION_PATTERNS = [
13882
14707
  // Audit O36: Tightened — require "no" followed by correction context (not "No problem", "No worries")
13883
14708
  /^no[,.\s!]+\s*(that'?s|it'?s|i\s|you|this|the|not|don'?t|stop|wrong)/i,
@@ -13953,6 +14778,19 @@ function killProcessGroup(proc, signal = "SIGTERM") {
13953
14778
  }
13954
14779
  }
13955
14780
  }
14781
+ function runCompaction(chatId, reason, onCompaction) {
14782
+ return summarizeWithFallbackChain(chatId).then((saved) => {
14783
+ if (saved) {
14784
+ return withChatLock(chatId, async () => {
14785
+ clearSession(chatId);
14786
+ clearUsage(chatId);
14787
+ onCompaction?.(chatId);
14788
+ });
14789
+ }
14790
+ }).catch((err) => {
14791
+ warn(`[agent] Compaction failed for ${chatId} (${reason}): ${err}`);
14792
+ });
14793
+ }
13956
14794
  function sweepStaleChatEntries() {
13957
14795
  for (const [chatId, state] of activeChats) {
13958
14796
  if (state.process && state.process.exitCode !== null) {
@@ -14010,6 +14848,13 @@ function stopAgent(chatId) {
14010
14848
  if (state.process) killProcessGroup(state.process, "SIGKILL");
14011
14849
  }, 2e3);
14012
14850
  }
14851
+ const modelToUnload = state.model;
14852
+ if (state.backendId === "ollama" && modelToUnload) {
14853
+ Promise.resolve().then(() => (init_ollama(), ollama_exports)).then(({ OllamaService }) => {
14854
+ OllamaService.tryUnloadModel(modelToUnload);
14855
+ }).catch(() => {
14856
+ });
14857
+ }
14013
14858
  return true;
14014
14859
  }
14015
14860
  function getGeminiFallback(model2) {
@@ -14021,7 +14866,8 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14021
14866
  const thinkingConfig = thinkingLevel && thinkingLevel !== "auto" ? adapter.applyThinkingConfig(thinkingLevel, model2) : void 0;
14022
14867
  const env = opts?.envOverride ? thinkingConfig?.envOverrides ? { ...opts.envOverride, ...thinkingConfig.envOverrides } : opts.envOverride : adapter.getEnv(thinkingConfig?.envOverrides);
14023
14868
  const finalArgs = thinkingConfig?.extraArgs ? [...config2.args, ...thinkingConfig.extraArgs] : config2.args;
14024
- log(`[agent:spawn] backend=${adapter.id} exe=${config2.executable} model=${model2} timeout=${effectiveTimeout / 1e3}s cwd=${config2.cwd ?? "(inherited)"}`);
14869
+ const hasResume = finalArgs.includes("--resume");
14870
+ log(`[agent:spawn] backend=${adapter.id} exe=${config2.executable} model=${model2} timeout=${effectiveTimeout / 1e3}s resume=${hasResume} cwd=${config2.cwd ?? "(inherited)"}`);
14025
14871
  const stdinMode = adapter.id === "codex" ? "pipe" : "ignore";
14026
14872
  const proc = spawn4(config2.executable, finalArgs, {
14027
14873
  env,
@@ -14130,7 +14976,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14130
14976
  for (const ev of events) {
14131
14977
  switch (ev.type) {
14132
14978
  case "init":
14133
- log(`[agent] Session init at ${elapsed()}`);
14979
+ log(`[agent] Session init at ${elapsed()} sessionId=${ev.sessionId ?? "(none)"}`);
14134
14980
  if (ev.sessionId) sessionId = ev.sessionId;
14135
14981
  resetContentSilenceTimer();
14136
14982
  break;
@@ -14228,7 +15074,6 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14228
15074
  input += ev.usage.input;
14229
15075
  output2 += ev.usage.output;
14230
15076
  cacheRead += ev.usage.cacheRead;
14231
- contextSize = ev.usage.input + (ev.usage.cacheRead ?? 0);
14232
15077
  }
14233
15078
  break;
14234
15079
  case "result":
@@ -14246,7 +15091,6 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14246
15091
  input = ev.usage.input;
14247
15092
  output2 = ev.usage.output;
14248
15093
  cacheRead = ev.usage.cacheRead;
14249
- contextSize = ev.usage.input + (ev.usage.cacheRead ?? 0);
14250
15094
  }
14251
15095
  if (adapter.shouldKillOnResult()) {
14252
15096
  try {
@@ -14329,7 +15173,8 @@ Partial output: ${accumulatedText.slice(-500)}`;
14329
15173
  return;
14330
15174
  }
14331
15175
  const cleanedResult = stripThinkingContent(resultText || accumulatedText);
14332
- resolve3({ resultText: cleanedResult, thinkingText: accumulatedThinking, sessionId, input, output: output2, cacheRead, contextSize, sawToolEvents, sawResultEvent });
15176
+ log(`[agent:spawn] Resolving: sessionId=${sessionId ?? "(none)"} hasText=${!!cleanedResult} code=${code}`);
15177
+ resolve3({ resultText: cleanedResult, thinkingText: accumulatedThinking, sessionId, input, output: output2, cacheRead, contextSize: null, sawToolEvents, sawResultEvent });
14333
15178
  });
14334
15179
  });
14335
15180
  }
@@ -14537,20 +15382,23 @@ async function askAgentImpl(chatId, userMessage, opts) {
14537
15382
  const responseStyle = getResponseStyle(settingsChat);
14538
15383
  const thinkingLevel = opts?.thinkingLevel ?? getThinkingLevel(settingsChat);
14539
15384
  const resolvedCwd = cwd ?? WORKSPACE_PATH;
14540
- const { entityType, bootstrapProfile: profile } = optsEntityType && optsProfile ? { entityType: optsEntityType, bootstrapProfile: optsProfile } : resolveFromLegacyTier(bootstrapTier ?? "full");
15385
+ const { entityType, bootstrapProfile: profile } = optsEntityType && optsProfile ? { entityType: optsEntityType, bootstrapProfile: optsProfile } : optsProfile ? { entityType: "main", bootstrapProfile: optsProfile } : resolveFromLegacyTier(bootstrapTier ?? "full");
14541
15386
  const effectiveAgentMode = optsAgentMode ?? getAgentMode(settingsChat);
14542
15387
  const sideQuestCtx = settingsSourceChatId ? { parentChatId: settingsSourceChatId, actualChatId: chatId } : void 0;
14543
15388
  const fullPrompt = await assembleBootstrapPrompt(userMessage, entityType, profile, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx, planningDirective, opts?.chatContext, adapter.type);
14544
15389
  if (adapter.streamDirect) {
14545
15390
  const resolvedModel2 = model2 ?? adapter.defaultModel;
14546
15391
  const abortController = new AbortController();
14547
- const cancelState2 = { cancelled: false, userMessage, abortController };
15392
+ const cancelState2 = { cancelled: false, userMessage, abortController, backendId: adapter.id, model: resolvedModel2 };
14548
15393
  activeChats.set(chatId, cancelState2);
14549
15394
  try {
14550
15395
  let messageHistory;
15396
+ let apiContextSize;
14551
15397
  if (adapter.type === "api") {
14552
15398
  const contextWindow = adapter.contextWindow[resolvedModel2] ?? 8192;
14553
- messageHistory = await buildApiMessages(chatId, userMessage, fullPrompt, contextWindow);
15399
+ const { buildApiMessages: buildMsgs, estimateContextUsage: estimateContextUsage2 } = await Promise.resolve().then(() => (init_api_context(), api_context_exports));
15400
+ messageHistory = await buildMsgs(chatId, userMessage, fullPrompt, contextWindow);
15401
+ apiContextSize = estimateContextUsage2(chatId, contextWindow).estimatedTokens;
14554
15402
  }
14555
15403
  const sdResult = await adapter.streamDirect(fullPrompt, resolvedModel2, {
14556
15404
  timeoutMs: timeoutMs ?? 3e5,
@@ -14566,23 +15414,29 @@ async function askAgentImpl(chatId, userMessage, opts) {
14566
15414
  });
14567
15415
  if (!isSyntheticChatId(chatId)) {
14568
15416
  appendToLog(chatId, userMessage, sdResult.text, adapter.id, resolvedModel2, null);
14569
- const AUTO_SUMMARIZE_THRESHOLD = 30;
14570
- const pairCount = profile !== "chat" ? getMessagePairCount(chatId) : 0;
14571
- if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
14572
- log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
14573
- summarizeWithFallbackChain(chatId).then((saved) => {
14574
- if (saved) {
14575
- clearSession(chatId);
14576
- opts?.onCompaction?.(chatId);
14577
- }
14578
- }).catch((err) => {
14579
- warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
14580
- });
15417
+ if (apiContextSize && adapter.type === "api" && !compactionInFlight.has(chatId)) {
15418
+ const contextWindow = adapter.contextWindow[resolvedModel2] ?? 8192;
15419
+ const contextPct = apiContextSize / contextWindow * 100;
15420
+ if (contextPct >= 85) {
15421
+ compactionInFlight.add(chatId);
15422
+ log(`[agent] Context at ${contextPct.toFixed(0)}% for ${chatId} \u2014 triggering background compaction`);
15423
+ opts?.onCompaction?.(chatId, "triggered");
15424
+ runCompaction(chatId, "context-85%", opts?.onCompaction).finally(() => {
15425
+ compactionInFlight.delete(chatId);
15426
+ });
15427
+ }
15428
+ }
15429
+ if (adapter.type !== "api" || !compactionInFlight.has(chatId)) {
15430
+ const pairCount = profile !== "chat" ? getMessagePairCount(chatId) : 0;
15431
+ if (pairCount >= 30) {
15432
+ log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
15433
+ runCompaction(chatId, "30-pair-threshold", opts?.onCompaction);
15434
+ }
14581
15435
  }
14582
15436
  }
14583
15437
  const sdUsage = sdResult.usage ?? { input: 0, output: 0 };
14584
15438
  if (sdUsage.input + sdUsage.output > 0) {
14585
- addUsage(chatId, sdUsage.input, sdUsage.output, 0, resolvedModel2);
15439
+ addUsage(chatId, sdUsage.input, sdUsage.output, 0, resolvedModel2, adapter.id, apiContextSize);
14586
15440
  }
14587
15441
  if (cancelState2.cancelled) {
14588
15442
  return { text: "Stopped.", usage: { input: sdUsage.input, output: sdUsage.output, cacheRead: 0 } };
@@ -14595,9 +15449,16 @@ async function askAgentImpl(chatId, userMessage, opts) {
14595
15449
  };
14596
15450
  } finally {
14597
15451
  activeChats.delete(chatId);
15452
+ if (adapter.id === "ollama" && !cancelState2.cancelled) {
15453
+ Promise.resolve().then(() => (init_ollama(), ollama_exports)).then(({ OllamaService }) => {
15454
+ OllamaService.tryUnloadModel(resolvedModel2);
15455
+ }).catch(() => {
15456
+ });
15457
+ }
14598
15458
  }
14599
15459
  }
14600
15460
  const existingSessionId = settingsSourceChatId ? null : getSessionId(settingsChat);
15461
+ log(`[agent:session] chatId=${chatId} settingsChat=${settingsChat} existingSessionId=${existingSessionId ?? "(none)"} backend=${adapter.id}`);
14601
15462
  const allowedTools = getEnabledTools(settingsChat);
14602
15463
  const mcpConfigPath = profile !== "minimal" && effectiveAgentMode !== "native" && MCP_CONFIG_FLAG[adapter.id] ? getMcpConfigPath(chatId) : null;
14603
15464
  const baseConfig = adapter.buildSpawnConfig({
@@ -14836,7 +15697,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
14836
15697
  return { text: "Stopped.", usage: { input: result.input, output: result.output, cacheRead: result.cacheRead, contextSize: result.contextSize } };
14837
15698
  }
14838
15699
  if (result.sessionId && !isSyntheticChatId(chatId)) {
15700
+ log(`[agent:session] Persisting session for chatId=${chatId}: ${result.sessionId}`);
14839
15701
  setSessionId(chatId, result.sessionId);
15702
+ } else if (!result.sessionId) {
15703
+ log(`[agent:session] No sessionId in result for chatId=${chatId} \u2014 session will not resume`);
14840
15704
  }
14841
15705
  if (adapter.id === BACKEND.CLAUDE && result.resultText && result.sessionId && /InputValidationError/i.test(result.resultText) && /missing|required parameter/i.test(result.resultText)) {
14842
15706
  const toolMatch = result.resultText.match(/(?:parameter\s+'|required parameter\s+['"]?)([\w-]+)/i);
@@ -14884,17 +15748,12 @@ async function askAgentImpl(chatId, userMessage, opts) {
14884
15748
  }
14885
15749
  if (result.resultText && !isSyntheticChatId(chatId)) {
14886
15750
  appendToLog(chatId, userMessage, result.resultText, adapter.id, model2 ?? null, result.sessionId ?? null);
14887
- const AUTO_SUMMARIZE_THRESHOLD = 30;
14888
15751
  const pairCount = profile !== "chat" ? getMessagePairCount(chatId) : 0;
14889
- if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
15752
+ if (pairCount >= 30 && !compactionInFlight.has(chatId)) {
15753
+ compactionInFlight.add(chatId);
14890
15754
  log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
14891
- summarizeWithFallbackChain(chatId).then((saved) => {
14892
- if (saved) {
14893
- clearSession(chatId);
14894
- opts?.onCompaction?.(chatId);
14895
- }
14896
- }).catch((err) => {
14897
- warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
15755
+ runCompaction(chatId, "30-pair-threshold", opts?.onCompaction).finally(() => {
15756
+ compactionInFlight.delete(chatId);
14898
15757
  });
14899
15758
  }
14900
15759
  }
@@ -14923,7 +15782,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
14923
15782
  if (!flag) return args;
14924
15783
  return [...args, ...flag, mcpConfigPath, "--strict-mcp-config"];
14925
15784
  }
14926
- var activeChats, staleSweepTimer, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_ERROR, FIRST_RESPONSE_TIMEOUT_ERROR, FREE_SLOTS_EXHAUSTED, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
15785
+ var activeChats, compactionInFlight, staleSweepTimer, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_ERROR, FIRST_RESPONSE_TIMEOUT_ERROR, FREE_SLOTS_EXHAUSTED, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
14927
15786
  var init_agent = __esm({
14928
15787
  "src/agent.ts"() {
14929
15788
  "use strict";
@@ -14940,7 +15799,6 @@ var init_agent = __esm({
14940
15799
  init_strip_thinking();
14941
15800
  init_text_utils();
14942
15801
  init_session_log();
14943
- init_api_context();
14944
15802
  init_summarize();
14945
15803
  init_quota();
14946
15804
  init_store5();
@@ -14951,6 +15809,7 @@ var init_agent = __esm({
14951
15809
  init_unified_config();
14952
15810
  init_mcp_config();
14953
15811
  activeChats = /* @__PURE__ */ new Map();
15812
+ compactionInFlight = /* @__PURE__ */ new Set();
14954
15813
  chatLocks = /* @__PURE__ */ new Map();
14955
15814
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
14956
15815
  FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
@@ -15118,7 +15977,9 @@ async function runWeeklySweep(chatId, channel, backendId, model2) {
15118
15977
  buttons.push([{ label: "Review Now", data: "mem:opt:start", style: "success" }]);
15119
15978
  }
15120
15979
  buttons.push([{ label: "Dismiss", data: "mem:sweep:dismiss" }]);
15121
- await sendOrEditKeyboard(chatId, channel, void 0, lines.join("\n"), buttons);
15980
+ if (channel) {
15981
+ await sendOrEditKeyboard(chatId, channel, void 0, lines.join("\n"), buttons);
15982
+ }
15122
15983
  return { suggestionsCount, cleanedUp };
15123
15984
  } catch (err) {
15124
15985
  const msg = errorMessage(err);
@@ -15438,52 +16299,6 @@ var init_pagination = __esm({
15438
16299
  }
15439
16300
  });
15440
16301
 
15441
- // src/format-time.ts
15442
- function formatLocalDate(utcDatetime) {
15443
- const d = parseUtcDatetime(utcDatetime);
15444
- if (!d) return utcDatetime.split("T")[0] ?? utcDatetime.split(" ")[0];
15445
- const year = d.getFullYear();
15446
- const month = String(d.getMonth() + 1).padStart(2, "0");
15447
- const day = String(d.getDate()).padStart(2, "0");
15448
- return `${year}-${month}-${day}`;
15449
- }
15450
- function formatLocalDateTime(utcDatetime) {
15451
- const d = parseUtcDatetime(utcDatetime);
15452
- if (!d) return utcDatetime;
15453
- const year = d.getFullYear();
15454
- const month = String(d.getMonth() + 1).padStart(2, "0");
15455
- const day = String(d.getDate()).padStart(2, "0");
15456
- const hours = String(d.getHours()).padStart(2, "0");
15457
- const minutes = String(d.getMinutes()).padStart(2, "0");
15458
- const tzAbbr = getTimezoneAbbr(d);
15459
- return `${year}-${month}-${day} ${hours}:${minutes} ${tzAbbr}`;
15460
- }
15461
- function parseUtcDatetime(utcDatetime) {
15462
- if (!utcDatetime) return null;
15463
- const normalized = utcDatetime.includes("T") ? utcDatetime : utcDatetime.replace(" ", "T");
15464
- const withZ = normalized.endsWith("Z") ? normalized : normalized + "Z";
15465
- const d = new Date(withZ);
15466
- return isNaN(d.getTime()) ? null : d;
15467
- }
15468
- function getTimezoneAbbr(date) {
15469
- try {
15470
- const parts = new Intl.DateTimeFormat("en-US", {
15471
- timeZone: systemTimezone,
15472
- timeZoneName: "short"
15473
- }).formatToParts(date);
15474
- return parts.find((p) => p.type === "timeZoneName")?.value ?? "";
15475
- } catch {
15476
- return "";
15477
- }
15478
- }
15479
- var systemTimezone;
15480
- var init_format_time = __esm({
15481
- "src/format-time.ts"() {
15482
- "use strict";
15483
- systemTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
15484
- }
15485
- });
15486
-
15487
16302
  // src/scheduler/humanize.ts
15488
16303
  function formatTime(hour, minute) {
15489
16304
  const h = hour % 12 || 12;
@@ -16776,11 +17591,11 @@ var init_health2 = __esm({
16776
17591
  });
16777
17592
 
16778
17593
  // src/health/checks.ts
16779
- import { existsSync as existsSync16, statSync as statSync5, readFileSync as readFileSync11 } from "fs";
17594
+ import { existsSync as existsSync16, statSync as statSync5, readFileSync as readFileSync12 } from "fs";
16780
17595
  import { execFileSync as execFileSync2, execSync as execSync2 } from "child_process";
16781
17596
  function getRecentErrors() {
16782
17597
  if (!existsSync16(ERROR_LOG_PATH)) return null;
16783
- const logContent = readFileSync11(ERROR_LOG_PATH, "utf-8");
17598
+ const logContent = readFileSync12(ERROR_LOG_PATH, "utf-8");
16784
17599
  const allLines = logContent.split("\n").filter(Boolean).slice(-500);
16785
17600
  const last24h = Date.now() - 864e5;
16786
17601
  const lines = allLines.filter((line) => {
@@ -17009,7 +17824,7 @@ __export(heartbeat_exports, {
17009
17824
  stopHeartbeatForChat: () => stopHeartbeatForChat,
17010
17825
  updateHeartbeatConfig: () => updateHeartbeatConfig
17011
17826
  });
17012
- import { readFileSync as readFileSync12, existsSync as existsSync17 } from "fs";
17827
+ import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
17013
17828
  import { join as join17 } from "path";
17014
17829
  function findHeartbeatJob() {
17015
17830
  try {
@@ -17191,7 +18006,7 @@ ${watchLines.join("\n")}`);
17191
18006
  }
17192
18007
  if (existsSync17(HEARTBEAT_MD_PATH)) {
17193
18008
  try {
17194
- const custom = readFileSync12(HEARTBEAT_MD_PATH, "utf-8").trim();
18009
+ const custom = readFileSync13(HEARTBEAT_MD_PATH, "utf-8").trim();
17195
18010
  if (custom) {
17196
18011
  sections.push(`[Custom checks from HEARTBEAT.md]
17197
18012
  ${custom}`);
@@ -17440,6 +18255,7 @@ __export(ui_exports, {
17440
18255
  ROTATION_MODE_LABELS: () => ROTATION_MODE_LABELS,
17441
18256
  buildAccountSlotKeyboard: () => buildAccountSlotKeyboard,
17442
18257
  doBackendSwitch: () => doBackendSwitch,
18258
+ executeBackendDisable: () => executeBackendDisable,
17443
18259
  getJobScheduleText: () => getJobScheduleText,
17444
18260
  getJobStatusEmoji: () => getJobStatusEmoji,
17445
18261
  getJobStatusLabel: () => getJobStatusLabel,
@@ -17447,6 +18263,7 @@ __export(ui_exports, {
17447
18263
  sendApiToolsKeyboard: () => sendApiToolsKeyboard,
17448
18264
  sendBackendAccountPicker: () => sendBackendAccountPicker,
17449
18265
  sendBackendConfigPanel: () => sendBackendConfigPanel,
18266
+ sendBackendDisableFlow: () => sendBackendDisableFlow,
17450
18267
  sendBackendModelPicker: () => sendBackendModelPicker,
17451
18268
  sendBackendPicker: () => sendBackendPicker,
17452
18269
  sendBackendSwitchConfirmation: () => sendBackendSwitchConfirmation,
@@ -18190,9 +19007,24 @@ ${unapproved.length} CC-Claw skill${unapproved.length !== 1 ? "s" : ""} need rev
18190
19007
  const safePage = Math.max(1, Math.min(page, totalPages));
18191
19008
  const start = (safePage - 1) * SKILLS_PER_PAGE;
18192
19009
  const pageSkills = approved.slice(start, start + SKILLS_PER_PAGE);
19010
+ const { getPinnedSkills: getPinnedSkills2 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
19011
+ const pinned = getPinnedSkills2(chatId);
19012
+ let correctedSkills = /* @__PURE__ */ new Set();
19013
+ try {
19014
+ const { getSkillCorrections: getSkillCorrections2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
19015
+ const db3 = (await Promise.resolve().then(() => (init_store5(), store_exports5))).getDb();
19016
+ const corrections = getSkillCorrections2(db3, 30, 3);
19017
+ correctedSkills = new Set(corrections.map((c) => c.skillName));
19018
+ } catch {
19019
+ }
18193
19020
  const typeIcon = (s) => s.type === "specialist" ? "\u{1F3AF}" : "\u{1F527}";
18194
19021
  const buttons = pageSkills.map((s) => {
18195
- return [{ label: `${typeIcon(s)} ${s.name}`, data: `skill:${s.source}:${s.name}`, style: "success" }];
19022
+ const isPinned = pinned.includes(s.name);
19023
+ const correctionBadge = correctedSkills.has(s.name) ? " \u26A0\uFE0F" : "";
19024
+ return [
19025
+ { label: `${typeIcon(s)} ${s.name}${correctionBadge}`, data: `skill:${s.source}:${s.name}`, style: "success" },
19026
+ { label: isPinned ? "\u{1F4CC}" : "\u25CB Pin", data: `pin:${s.name}` }
19027
+ ];
18196
19028
  });
18197
19029
  if (totalPages > 1) {
18198
19030
  const navRow = [];
@@ -18924,6 +19756,17 @@ async function sendBackendPicker(chatId, channel, messageId) {
18924
19756
  }
18925
19757
  async function sendBackendConfigPanel(chatId, backendId, channel, messageId, switched = false) {
18926
19758
  if (typeof channel.sendKeyboard !== "function") return;
19759
+ const disabled = getDisabledBackends().includes(backendId);
19760
+ if (disabled) {
19761
+ const adapter = getAdapter(backendId);
19762
+ const text = `\u{1F534} ${adapter.displayName} is disabled`;
19763
+ const buttons2 = [
19764
+ [{ label: "Enable", data: `bconf:enable:${backendId}`, style: "success" }],
19765
+ [{ label: "\u2190 Back to Backends", data: "bconf:back" }]
19766
+ ];
19767
+ await sendOrEditKeyboard(chatId, channel, messageId, text, buttons2);
19768
+ return;
19769
+ }
18927
19770
  const summary = backendConfigSummary(chatId, backendId, switched);
18928
19771
  const buttons = [
18929
19772
  [
@@ -18931,10 +19774,80 @@ async function sendBackendConfigPanel(chatId, backendId, channel, messageId, swi
18931
19774
  { label: "Thinking", data: `bconf:thinking:${backendId}` },
18932
19775
  { label: "Account", data: `bconf:account:${backendId}` }
18933
19776
  ],
19777
+ [{ label: "Disable", data: `bconf:disable:${backendId}`, style: "danger" }],
18934
19778
  [{ label: "\u2190 Back to Backends", data: "bconf:back" }]
18935
19779
  ];
18936
19780
  await sendOrEditKeyboard(chatId, channel, messageId, summary, buttons);
18937
19781
  }
19782
+ async function sendBackendDisableFlow(chatId, backendId, channel, messageId) {
19783
+ if (typeof channel.sendKeyboard !== "function") return;
19784
+ const { getActiveJobsByBackend: getActiveJobsByBackend2 } = await Promise.resolve().then(() => (init_jobs(), jobs_exports));
19785
+ const affectedJobs = getActiveJobsByBackend2(backendId);
19786
+ if (affectedJobs.length > 0) {
19787
+ await sendDisableJobReassign(chatId, backendId, affectedJobs, channel, messageId);
19788
+ return;
19789
+ }
19790
+ const current = getBackend(chatId);
19791
+ if (current === backendId) {
19792
+ await sendDisableBackendSwitch(chatId, backendId, channel, messageId);
19793
+ return;
19794
+ }
19795
+ await executeBackendDisable(chatId, backendId, channel, messageId);
19796
+ }
19797
+ async function sendDisableJobReassign(chatId, backendId, jobs, channel, messageId) {
19798
+ const adapter = getAdapter(backendId);
19799
+ const jobList = jobs.slice(0, 5).map((j) => ` \u2022 ${j.title ?? j.description}`).join("\n");
19800
+ const extra = jobs.length > 5 ? `
19801
+ \u2026and ${jobs.length - 5} more` : "";
19802
+ const text = [
19803
+ `\u26A0\uFE0F ${jobs.length} cron job(s) use ${adapter.displayName}:`,
19804
+ "",
19805
+ jobList + extra,
19806
+ "",
19807
+ "Reassign these jobs to:"
19808
+ ].join("\n");
19809
+ const ids = getAvailableChatBackendIds().filter((id) => id !== backendId);
19810
+ const buttons = ids.map((id) => [{
19811
+ label: getAdapter(id).displayName,
19812
+ data: `bconf:reassign:${backendId}:${id}`
19813
+ }]);
19814
+ buttons.push([{ label: "Cancel", data: `bconf:panel:${backendId}` }]);
19815
+ await sendOrEditKeyboard(chatId, channel, messageId, text, buttons);
19816
+ }
19817
+ async function sendDisableBackendSwitch(chatId, backendId, channel, messageId) {
19818
+ const adapter = getAdapter(backendId);
19819
+ const text = `${adapter.displayName} is your active backend.
19820
+
19821
+ Switch to:`;
19822
+ const ids = getAvailableChatBackendIds().filter((id) => id !== backendId);
19823
+ const buttons = ids.map((id) => [{
19824
+ label: getAdapter(id).displayName,
19825
+ data: `bconf:disableswitch:${backendId}:${id}`
19826
+ }]);
19827
+ buttons.push([{ label: "Cancel", data: `bconf:panel:${backendId}` }]);
19828
+ await sendOrEditKeyboard(chatId, channel, messageId, text, buttons);
19829
+ }
19830
+ async function executeBackendDisable(chatId, backendId, channel, messageId, opts) {
19831
+ const adapter = getAdapter(backendId);
19832
+ const parts = [];
19833
+ if (opts?.reassignTo) {
19834
+ const { reassignJobsBackend: reassignJobsBackend2 } = await Promise.resolve().then(() => (init_jobs(), jobs_exports));
19835
+ const count = reassignJobsBackend2(backendId, opts.reassignTo);
19836
+ const targetName = getAdapter(opts.reassignTo).displayName;
19837
+ if (count > 0) parts.push(`${count} job(s) reassigned to ${targetName}`);
19838
+ }
19839
+ if (opts?.switchTo) {
19840
+ await doBackendSwitch(chatId, opts.switchTo, channel, { skipContext: true });
19841
+ parts.push(`Switched to ${getAdapter(opts.switchTo).displayName}`);
19842
+ }
19843
+ setBackendDisabled(backendId, true);
19844
+ const detail = parts.length > 0 ? `
19845
+ ${parts.join(". ")}.` : "";
19846
+ const text = `\u2705 ${adapter.displayName} disabled.${detail}`;
19847
+ await sendOrEditKeyboard(chatId, channel, messageId, text, [
19848
+ [{ label: "\u2190 Back to Backends", data: "bconf:back" }]
19849
+ ]);
19850
+ }
18938
19851
  async function sendBackendModelPicker(chatId, backendId, channel, messageId) {
18939
19852
  if (typeof channel.sendKeyboard !== "function") return;
18940
19853
  const adapter = getAdapter(backendId);
@@ -19008,7 +19921,15 @@ async function sendBackendThinkingPicker(chatId, backendId, channel, messageId)
19008
19921
  return;
19009
19922
  }
19010
19923
  const current = getThinkingLevel(chatId) ?? "auto";
19011
- const levels = [
19924
+ const LEVEL_LABELS = {
19925
+ auto: "Auto",
19926
+ off: "Off",
19927
+ low: "Low",
19928
+ medium: "Medium",
19929
+ high: backendId === "ollama" ? "On" : "High",
19930
+ extra_high: "Extra High"
19931
+ };
19932
+ const defaultLevels = [
19012
19933
  ["auto", "Auto"],
19013
19934
  ["off", "Off"],
19014
19935
  ["low", "Low"],
@@ -19016,6 +19937,7 @@ async function sendBackendThinkingPicker(chatId, backendId, channel, messageId)
19016
19937
  ["high", "High"],
19017
19938
  ["extra_high", "Extra High"]
19018
19939
  ];
19940
+ const levels = modelInfo?.thinkingLevels ? modelInfo.thinkingLevels.map((l) => [l, LEVEL_LABELS[l] ?? capitalize(l.replace("_", " "))]) : defaultLevels;
19019
19941
  const buttons = levels.map(([val, label2]) => [{
19020
19942
  label: `${val === current ? "\u2713 " : ""}${label2}`,
19021
19943
  data: `bconf:setthinking:${backendId}:${val}`,
@@ -19076,6 +19998,16 @@ async function doBackendSwitch(chatId, backendId, channel, opts) {
19076
19998
  const bridge = buildContextBridge(chatId, 15, interrupted);
19077
19999
  if (bridge) setPendingContextBridge(chatId, bridge);
19078
20000
  }
20001
+ const previousBackend = getBackend(chatId);
20002
+ if (previousBackend === "ollama") {
20003
+ const previousModel = getModel(chatId);
20004
+ if (previousModel) {
20005
+ Promise.resolve().then(() => (init_ollama(), ollama_exports)).then(({ OllamaService }) => {
20006
+ OllamaService.tryUnloadModel(previousModel);
20007
+ }).catch(() => {
20008
+ });
20009
+ }
20010
+ }
19079
20011
  clearSession(chatId);
19080
20012
  clearModel(chatId);
19081
20013
  clearThinkingLevel(chatId);
@@ -20566,7 +21498,7 @@ __export(analyze_exports, {
20566
21498
  });
20567
21499
  import { spawn as spawn5 } from "child_process";
20568
21500
  import { createInterface as createInterface4 } from "readline";
20569
- import { readFileSync as readFileSync13, existsSync as existsSync18, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
21501
+ import { readFileSync as readFileSync14, existsSync as existsSync18, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
20570
21502
  import { join as join18 } from "path";
20571
21503
  import { homedir as homedir6 } from "os";
20572
21504
  function applySignalDecay(confidence, createdAt) {
@@ -20588,7 +21520,7 @@ function discoverReflectionTargets() {
20588
21520
  if (!existsSync18(skillFile)) continue;
20589
21521
  let desc = "skill";
20590
21522
  try {
20591
- const content = readFileSync13(skillFile, "utf-8");
21523
+ const content = readFileSync14(skillFile, "utf-8");
20592
21524
  const descMatch = content.match(/description:\s*["']?([^"'\n]+)/);
20593
21525
  if (descMatch) desc = descMatch[1].trim().slice(0, 80);
20594
21526
  } catch {
@@ -20663,6 +21595,21 @@ ${categoryList}`);
20663
21595
  sections.push(skill.content);
20664
21596
  }
20665
21597
  }
21598
+ if (params.skillCorrections && params.skillCorrections.length > 0) {
21599
+ sections.push("[Underperforming Skills]");
21600
+ sections.push(
21601
+ "These skills have received repeated corrections. The per-model breakdown helps distinguish skill quality issues from model capability limits. If corrections concentrate on weaker models (Flash, Haiku, Lite) while stronger models (Opus, Pro) handle the skill correctly, this likely reflects model capability rather than skill quality \u2014 consider adding a simplified-instructions section for weaker models rather than a full rewrite."
21602
+ );
21603
+ for (const sc of params.skillCorrections) {
21604
+ const modelSummary = sc.modelBreakdown.map((m) => `${m.model}: ${m.count}`).join(", ");
21605
+ sections.push(`- **${sc.skillName}** \u2014 ${sc.correctionCount} corrections in last 30 days`);
21606
+ sections.push(` Models: ${modelSummary || "unknown"}`);
21607
+ const uniqueContexts = [...new Set(sc.contexts)].slice(0, 5);
21608
+ for (const ctx of uniqueContexts) {
21609
+ sections.push(` Correction: "${ctx}"`);
21610
+ }
21611
+ }
21612
+ }
20666
21613
  sections.push("[Previously Applied Insights]");
20667
21614
  if (appliedInsights.length === 0) {
20668
21615
  sections.push("(none)");
@@ -20797,7 +21744,7 @@ function resolveReflectionAdapter(chatId) {
20797
21744
  }
20798
21745
  function readIdentityFile(filename) {
20799
21746
  try {
20800
- return readFileSync13(join18(IDENTITY_PATH, filename), "utf-8");
21747
+ return readFileSync14(join18(IDENTITY_PATH, filename), "utf-8");
20801
21748
  } catch {
20802
21749
  return "";
20803
21750
  }
@@ -20956,7 +21903,7 @@ async function runAnalysisImpl(chatId, opts) {
20956
21903
  try {
20957
21904
  const fullPath = join18(ccClawHome, target.path);
20958
21905
  if (existsSync18(fullPath)) {
20959
- const content = readFileSync13(fullPath, "utf-8");
21906
+ const content = readFileSync14(fullPath, "utf-8");
20960
21907
  if (totalSkillChars + content.length > SKILL_CONTENT_CAP) break;
20961
21908
  skillContents.push({ path: target.path, content });
20962
21909
  totalSkillChars += content.length;
@@ -20964,6 +21911,7 @@ async function runAnalysisImpl(chatId, opts) {
20964
21911
  } catch {
20965
21912
  }
20966
21913
  }
21914
+ const skillCorrections = getSkillCorrections(db3, 30, 3);
20967
21915
  const prompt = buildAnalysisPrompt({
20968
21916
  signals: signals.map((s) => {
20969
21917
  const decayedConfidence = applySignalDecay(s.confidence, s.created_at);
@@ -20986,7 +21934,8 @@ async function runAnalysisImpl(chatId, opts) {
20986
21934
  })),
20987
21935
  codebaseEnabled,
20988
21936
  availableTargets,
20989
- skillContents
21937
+ skillContents,
21938
+ skillCorrections
20990
21939
  });
20991
21940
  const resolved = resolveReflectionAdapter(chatId);
20992
21941
  if (!resolved) {
@@ -21336,7 +22285,7 @@ __export(apply_exports, {
21336
22285
  isTargetAllowed: () => isTargetAllowed,
21337
22286
  rollbackInsight: () => rollbackInsight
21338
22287
  });
21339
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync7, existsSync as existsSync19, mkdirSync as mkdirSync7, readdirSync as readdirSync8, unlinkSync as unlinkSync5 } from "fs";
22288
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, existsSync as existsSync19, mkdirSync as mkdirSync7, readdirSync as readdirSync8, unlinkSync as unlinkSync5 } from "fs";
21340
22289
  import { join as join19, dirname as dirname4 } from "path";
21341
22290
  function isTargetAllowed(relativePath) {
21342
22291
  if (relativePath.includes("..")) return false;
@@ -21424,7 +22373,7 @@ async function applyInsight(insightId) {
21424
22373
  const absolutePath = join19(CC_CLAW_HOME, insight.targetFile);
21425
22374
  if (insight.proposedAction === "append" && insight.targetFile === "identity/SOUL.md") {
21426
22375
  if (existsSync19(absolutePath)) {
21427
- const currentContent = readFileSync14(absolutePath, "utf-8");
22376
+ const currentContent = readFileSync15(absolutePath, "utf-8");
21428
22377
  const lineCount = currentContent.split("\n").length;
21429
22378
  if (lineCount >= SOUL_LINE_CAP) {
21430
22379
  return {
@@ -21446,7 +22395,7 @@ async function applyInsight(insightId) {
21446
22395
  }
21447
22396
  let original = "";
21448
22397
  if (existsSync19(absolutePath)) {
21449
- original = readFileSync14(absolutePath, "utf-8");
22398
+ original = readFileSync15(absolutePath, "utf-8");
21450
22399
  } else if (insight.proposedAction !== "create") {
21451
22400
  return { success: false, message: `Target file "${insight.targetFile}" does not exist` };
21452
22401
  }
@@ -21587,7 +22536,7 @@ function computeLineDrift(baseline, absolutePath) {
21587
22536
  let current = "";
21588
22537
  try {
21589
22538
  if (existsSync19(absolutePath)) {
21590
- current = readFileSync14(absolutePath, "utf-8");
22539
+ current = readFileSync15(absolutePath, "utf-8");
21591
22540
  }
21592
22541
  } catch {
21593
22542
  return 1;
@@ -22319,6 +23268,14 @@ var init_api_mcp = __esm({
22319
23268
 
22320
23269
  // src/backends/api-common.ts
22321
23270
  import { streamText, stepCountIs, NoOutputGeneratedError, APICallError } from "ai";
23271
+ function mapToolActionToApiFormat(cb) {
23272
+ if (!cb) return void 0;
23273
+ return (event) => {
23274
+ const input = event.input ?? {};
23275
+ const result = event.type === "tool_end" ? typeof event.output === "string" ? event.output : event.output != null ? JSON.stringify(event.output) : "" : void 0;
23276
+ cb(event.toolName, input, result);
23277
+ };
23278
+ }
22322
23279
  function toModelMessage(msg) {
22323
23280
  switch (msg.role) {
22324
23281
  case "system":
@@ -22515,6 +23472,7 @@ var init_ollama2 = __esm({
22515
23472
  "src/backends/ollama.ts"() {
22516
23473
  "use strict";
22517
23474
  init_api_common();
23475
+ init_api_common();
22518
23476
  init_ollama();
22519
23477
  init_prompt();
22520
23478
  OLLAMA_HTTP_SENTINEL = "__ollama_http__";
@@ -22602,26 +23560,35 @@ var init_ollama2 = __esm({
22602
23560
  */
22603
23561
  async streamDirect(prompt, model2, opts) {
22604
23562
  const cleanPrompt = stripForLocalModel(prompt);
22605
- let disableThinking = false;
23563
+ let modelContextWindow;
23564
+ const ollamaProviderOpts = {};
22606
23565
  try {
22607
23566
  const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
22608
23567
  const modelRecord = OllamaStore.getModelByName(model2);
22609
- if (modelRecord?.forceThinkOff) {
22610
- disableThinking = true;
22611
- } else if (opts?.thinkingLevel === "off") {
22612
- disableThinking = true;
23568
+ const chatLevel = opts?.thinkingLevel;
23569
+ if (chatLevel === "off") {
23570
+ ollamaProviderOpts.think = false;
23571
+ } else if (chatLevel && chatLevel !== "auto") {
23572
+ ollamaProviderOpts.think = true;
23573
+ } else if (modelRecord?.forceThinkOff) {
23574
+ ollamaProviderOpts.think = false;
23575
+ }
23576
+ if (modelRecord?.contextWindow && modelRecord.contextWindow > 4096) {
23577
+ modelContextWindow = modelRecord.contextWindow;
22613
23578
  }
22614
23579
  } catch {
22615
23580
  }
23581
+ if (modelContextWindow) ollamaProviderOpts.num_ctx = modelContextWindow;
22616
23582
  const apiOpts = {
22617
23583
  timeoutMs: opts?.timeoutMs,
22618
23584
  onStream: opts?.onStream,
22619
23585
  signal: opts?.signal,
22620
23586
  messageHistory: opts?.messageHistory,
23587
+ onToolAction: mapToolActionToApiFormat(opts?.onToolAction),
22621
23588
  permMode: opts?.permMode,
22622
23589
  thinkingLevel: opts?.thinkingLevel,
22623
23590
  onThinking: opts?.onThinking,
22624
- ...disableThinking ? { providerOptions: { ollama: { think: false } } } : {}
23591
+ ...Object.keys(ollamaProviderOpts).length > 0 ? { providerOptions: { ollama: ollamaProviderOpts } } : {}
22625
23592
  };
22626
23593
  const result = await this.streamDirectWithHistory(
22627
23594
  cleanPrompt,
@@ -22646,10 +23613,11 @@ var init_ollama2 = __esm({
22646
23613
  const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
22647
23614
  const models = OllamaStore.getAvailableModels();
22648
23615
  for (const m of models) {
22649
- const isThinkingCapable = m.capability === "thinking" && !m.forceThinkOff;
22650
23616
  this.availableModels[m.name] = {
22651
23617
  label: `${m.name}${m.parameterSize ? ` (${m.parameterSize})` : ""}`,
22652
- thinking: isThinkingCapable ? "adjustable" : "none"
23618
+ thinking: "adjustable",
23619
+ // all Ollama models support think: true/false toggle
23620
+ thinkingLevels: ["auto", "off", "high"]
22653
23621
  };
22654
23622
  this.pricing[m.name] = { in: 0, out: 0, cache: 0 };
22655
23623
  this.contextWindow[m.name] = m.contextWindow ?? 4096;
@@ -22666,6 +23634,7 @@ var init_openrouter = __esm({
22666
23634
  "src/backends/openrouter.ts"() {
22667
23635
  "use strict";
22668
23636
  init_api_common();
23637
+ init_api_common();
22669
23638
  OPENROUTER_HTTP_SENTINEL = "__openrouter_api__";
22670
23639
  DEFAULT_FREE_MODEL = "qwen/qwen3.6-plus:free";
22671
23640
  OpenRouterAdapter = class extends ApiBackendBase {
@@ -22707,7 +23676,28 @@ var init_openrouter = __esm({
22707
23676
  }
22708
23677
  summarizerModel = DEFAULT_FREE_MODEL;
22709
23678
  pricing = {};
22710
- contextWindow = {};
23679
+ _contextWindowCache = null;
23680
+ _contextWindowCacheSize = 0;
23681
+ get contextWindow() {
23682
+ try {
23683
+ const { getApiModels: getApiModels2 } = (init_api_models(), __toCommonJS(api_models_exports));
23684
+ const models = getApiModels2("openrouter");
23685
+ if (this._contextWindowCache && models.length === this._contextWindowCacheSize) {
23686
+ return this._contextWindowCache;
23687
+ }
23688
+ const result = {};
23689
+ for (const m of models) {
23690
+ if (m.contextWindow) {
23691
+ result[m.modelId] = m.contextWindow;
23692
+ }
23693
+ }
23694
+ this._contextWindowCache = result;
23695
+ this._contextWindowCacheSize = models.length;
23696
+ return result;
23697
+ } catch {
23698
+ return {};
23699
+ }
23700
+ }
22711
23701
  // ── Vercel AI SDK provider ────────────────────────────────────────
22712
23702
  /**
22713
23703
  * Create the Vercel AI SDK LanguageModel for a given model ID.
@@ -22734,17 +23724,12 @@ var init_openrouter = __esm({
22734
23724
  * including tool actions, permission mode, thinking tokens, and conversation history.
22735
23725
  */
22736
23726
  async streamDirect(prompt, model2, opts) {
22737
- const onToolAction = opts?.onToolAction ? (event) => {
22738
- const input = event.input ?? {};
22739
- const result2 = event.type === "tool_end" ? typeof event.output === "string" ? event.output : JSON.stringify(event.output ?? "") : void 0;
22740
- opts.onToolAction(event.toolName, input, result2);
22741
- } : void 0;
22742
23727
  const apiOpts = {
22743
23728
  timeoutMs: opts?.timeoutMs,
22744
23729
  onStream: opts?.onStream,
22745
23730
  signal: opts?.signal,
22746
23731
  messageHistory: opts?.messageHistory,
22747
- onToolAction,
23732
+ onToolAction: mapToolActionToApiFormat(opts?.onToolAction),
22748
23733
  permMode: opts?.permMode,
22749
23734
  thinkingLevel: opts?.thinkingLevel,
22750
23735
  onThinking: opts?.onThinking
@@ -22920,7 +23905,10 @@ function getAvailableBackendIds() {
22920
23905
  return [...availableSet];
22921
23906
  }
22922
23907
  function getAvailableChatBackendIds() {
22923
- return CHAT_BACKEND_IDS.filter((id) => availableSet.size === 0 || availableSet.has(id));
23908
+ const disabled = getDisabledBackends();
23909
+ return CHAT_BACKEND_IDS.filter(
23910
+ (id) => (availableSet.size === 0 || availableSet.has(id)) && !disabled.includes(id)
23911
+ );
22924
23912
  }
22925
23913
  var openRouterAdapter, adapters, CHAT_BACKEND_IDS, availableSet;
22926
23914
  var init_backends = __esm({
@@ -22933,6 +23921,7 @@ var init_backends = __esm({
22933
23921
  init_ollama2();
22934
23922
  init_openrouter();
22935
23923
  init_store5();
23924
+ init_schema();
22936
23925
  init_log();
22937
23926
  init_types();
22938
23927
  openRouterAdapter = new OpenRouterAdapter();
@@ -23222,7 +24211,7 @@ var init_retry = __esm({
23222
24211
  });
23223
24212
 
23224
24213
  // src/bootstrap/profile.ts
23225
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync8, existsSync as existsSync22 } from "fs";
24214
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync8, existsSync as existsSync22 } from "fs";
23226
24215
  import { join as join21 } from "path";
23227
24216
  function hasActiveProfile(chatId) {
23228
24217
  return activeProfiles.has(chatId);
@@ -23353,7 +24342,7 @@ function extractUserUpdates(text) {
23353
24342
  }
23354
24343
  function appendToUserProfile(key, value) {
23355
24344
  if (!existsSync22(USER_PATH2)) return;
23356
- const content = readFileSync15(USER_PATH2, "utf-8");
24345
+ const content = readFileSync16(USER_PATH2, "utf-8");
23357
24346
  const line = `- **${key}**: ${value}`;
23358
24347
  if (content.includes(line)) return;
23359
24348
  const updated = content.trimEnd() + `
@@ -24806,6 +25795,7 @@ var init_live_status = __esm({
24806
25795
  /** Spinner frame counter — advances on each flush for animation. */
24807
25796
  spinnerFrame = 0;
24808
25797
  /** Timestamp of last successful edit — used for heartbeat force-through. */
25798
+ lastSuccessfulFlushAt = 0;
24809
25799
  /** Callback to restart typing indicator as fallback. */
24810
25800
  onTypingFallback;
24811
25801
  /** Set a callback that restarts the typing indicator loop as a fallback. */
@@ -26655,12 +27645,12 @@ async function handleEvolveCallback(chatId, data, channel, messageId) {
26655
27645
  );
26656
27646
  break;
26657
27647
  }
26658
- const { readFileSync: readFileSync35, existsSync: existsSync62 } = await import("fs");
27648
+ const { readFileSync: readFileSync36, existsSync: existsSync62 } = await import("fs");
26659
27649
  const { join: join42 } = await import("path");
26660
27650
  const targetPath = join42(homedir7(), ".cc-claw", insight.targetFile);
26661
27651
  let previewText;
26662
27652
  if (existsSync62(targetPath)) {
26663
- const current = readFileSync35(targetPath, "utf-8");
27653
+ const current = readFileSync36(targetPath, "utf-8");
26664
27654
  const diffLines = insight.proposedDiff.split("\n");
26665
27655
  const additions = diffLines.filter((l) => l.startsWith("+")).map((l) => ` + ${l.slice(1).trim()}`);
26666
27656
  const removals = diffLines.filter((l) => l.startsWith("-")).map((l) => ` - ${l.slice(1).trim()}`);
@@ -26732,13 +27722,13 @@ async function handleEvolveCallback(chatId, data, channel, messageId) {
26732
27722
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
26733
27723
  const current = getReflectionStatus2(getDb(), chatId);
26734
27724
  if (current === "frozen") {
26735
- const { readFileSync: readFileSync35, existsSync: existsSync62 } = await import("fs");
27725
+ const { readFileSync: readFileSync36, existsSync: existsSync62 } = await import("fs");
26736
27726
  const { join: join42 } = await import("path");
26737
27727
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
26738
27728
  const soulPath = join42(CC_CLAW_HOME3, "identity/SOUL.md");
26739
27729
  const userPath = join42(CC_CLAW_HOME3, "identity/USER.md");
26740
- const soul = existsSync62(soulPath) ? readFileSync35(soulPath, "utf-8") : "";
26741
- const user = existsSync62(userPath) ? readFileSync35(userPath, "utf-8") : "";
27730
+ const soul = existsSync62(soulPath) ? readFileSync36(soulPath, "utf-8") : "";
27731
+ const user = existsSync62(userPath) ? readFileSync36(userPath, "utf-8") : "";
26742
27732
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
26743
27733
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
26744
27734
  await sendEvolveDashboard(chatId, reflChatId, channel, messageId);
@@ -26877,11 +27867,11 @@ var init_evolve2 = __esm({
26877
27867
  });
26878
27868
 
26879
27869
  // src/optimizer/identity-audit.ts
26880
- import { readFileSync as readFileSync16, existsSync as existsSync26, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
27870
+ import { readFileSync as readFileSync17, existsSync as existsSync26, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
26881
27871
  import { join as join26 } from "path";
26882
27872
  function readIdentityFile2(filename) {
26883
27873
  try {
26884
- return readFileSync16(join26(IDENTITY_PATH, filename), "utf-8");
27874
+ return readFileSync17(join26(IDENTITY_PATH, filename), "utf-8");
26885
27875
  } catch {
26886
27876
  return "";
26887
27877
  }
@@ -27034,7 +28024,7 @@ var init_identity_audit = __esm({
27034
28024
  });
27035
28025
 
27036
28026
  // src/optimizer/skill-audit.ts
27037
- import { readFileSync as readFileSync17, existsSync as existsSync27 } from "fs";
28027
+ import { readFileSync as readFileSync18, existsSync as existsSync27 } from "fs";
27038
28028
  import { join as join27, basename as basename3 } from "path";
27039
28029
  function parseFrontmatter3(content) {
27040
28030
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
@@ -27080,7 +28070,7 @@ function detectDependentSkills(content) {
27080
28070
  return Array.from(deps);
27081
28071
  }
27082
28072
  function computeSkillStats(skillPath) {
27083
- const content = readFileSync17(skillPath, "utf-8");
28073
+ const content = readFileSync18(skillPath, "utf-8");
27084
28074
  const lines = content.split("\n");
27085
28075
  return {
27086
28076
  skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join27(skillPath, "..")) : basename3(skillPath, ".md"),
@@ -27109,7 +28099,7 @@ function loadDependentSkillContents(depNames, ccClawSkillsDir) {
27109
28099
  for (const candidate of candidates) {
27110
28100
  if (existsSync27(candidate)) {
27111
28101
  try {
27112
- const content = readFileSync17(candidate, "utf-8");
28102
+ const content = readFileSync18(candidate, "utf-8");
27113
28103
  results.push({
27114
28104
  name,
27115
28105
  content: content.length > 3e3 ? content.slice(0, 3e3) + "\n[...truncated]" : content
@@ -27253,7 +28243,7 @@ __export(analyze_exports2, {
27253
28243
  });
27254
28244
  import { spawn as spawn7 } from "child_process";
27255
28245
  import { createInterface as createInterface7 } from "readline";
27256
- import { readFileSync as readFileSync18, existsSync as existsSync28, readdirSync as readdirSync12 } from "fs";
28246
+ import { readFileSync as readFileSync19, existsSync as existsSync28, readdirSync as readdirSync12 } from "fs";
27257
28247
  import { join as join28 } from "path";
27258
28248
  import { homedir as homedir8 } from "os";
27259
28249
  function parseOptimizeOutput(raw, validAreas) {
@@ -27384,7 +28374,7 @@ function getModelDisplayInfo(chatId) {
27384
28374
  }
27385
28375
  function readIdentityFile3(filename) {
27386
28376
  try {
27387
- return readFileSync18(join28(IDENTITY_PATH, filename), "utf-8");
28377
+ return readFileSync19(join28(IDENTITY_PATH, filename), "utf-8");
27388
28378
  } catch {
27389
28379
  return "";
27390
28380
  }
@@ -27397,7 +28387,7 @@ function loadContextFiles() {
27397
28387
  for (const entry of readdirSync12(contextDir)) {
27398
28388
  if (!entry.endsWith(".md")) continue;
27399
28389
  try {
27400
- const content = readFileSync18(join28(contextDir, entry), "utf-8");
28390
+ const content = readFileSync19(join28(contextDir, entry), "utf-8");
27401
28391
  results.push({ name: entry, content });
27402
28392
  } catch {
27403
28393
  }
@@ -27449,7 +28439,7 @@ async function runSkillAudit(chatId, skillPath) {
27449
28439
  log(`[optimizer] Running skill audit on ${stats.skillName} with ${adapter.id}:${model2}`);
27450
28440
  const soulMd = readIdentityFile3("SOUL.md");
27451
28441
  const ccClawSkillsDir = join28(homedir8(), ".cc-claw", "workspace", "skills");
27452
- const skillContent = readFileSync18(skillPath, "utf-8");
28442
+ const skillContent = readFileSync19(skillPath, "utf-8");
27453
28443
  const prompt = buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir);
27454
28444
  const raw = await spawnAnalysis2(adapter, model2, prompt);
27455
28445
  const findings = parseOptimizeOutput(raw, VALID_SKILL_AREAS);
@@ -27472,7 +28462,7 @@ function listCcClawSkills() {
27472
28462
  if (!existsSync28(skillFile)) continue;
27473
28463
  let description = "skill";
27474
28464
  try {
27475
- const content = readFileSync18(skillFile, "utf-8");
28465
+ const content = readFileSync19(skillFile, "utf-8");
27476
28466
  const descMatch = content.match(/description:\s*>?\s*\n?\s*(.+)/);
27477
28467
  if (descMatch) description = descMatch[1].trim().slice(0, 60);
27478
28468
  } catch {
@@ -27799,7 +28789,7 @@ __export(optimize_exports2, {
27799
28789
  handleOptimizeCallback: () => handleOptimizeCallback,
27800
28790
  handleOptimizeCommand: () => handleOptimizeCommand
27801
28791
  });
27802
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync9, existsSync as existsSync29, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
28792
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync9, existsSync as existsSync29, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
27803
28793
  import { join as join29, dirname as dirname5 } from "path";
27804
28794
  import { homedir as homedir9 } from "os";
27805
28795
  async function handleOptimizeCommand(chatId, channel, _args, messageId) {
@@ -28029,7 +29019,7 @@ async function applyFinding(chatId, channel, index, messageId) {
28029
29019
  await showFinding(chatId, channel, index + 1, effectiveMsgId);
28030
29020
  return;
28031
29021
  }
28032
- const original = readFileSync19(targetPath, "utf-8");
29022
+ const original = readFileSync20(targetPath, "utf-8");
28033
29023
  const backupPath = targetPath + `.bak.${Date.now()}`;
28034
29024
  writeFileSync9(backupPath, original, "utf-8");
28035
29025
  pruneBackups2(targetPath);
@@ -29358,6 +30348,7 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
29358
30348
  const summarized = await summarizeSession(chatId);
29359
30349
  clearSession(chatId);
29360
30350
  clearChatPaidSlots(chatId);
30351
+ clearUsage(chatId);
29361
30352
  setSessionStartedAt(chatId);
29362
30353
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "New session started", detail: { field: "session", action: "reset", summarized } });
29363
30354
  if (typeof channel.sendKeyboard === "function" && oldSessionId) {
@@ -29411,6 +30402,7 @@ async function handleClearCommand(chatId, _commandArgs, _msg, channel) {
29411
30402
  stopAllSideQuests(chatId);
29412
30403
  clearSession(chatId);
29413
30404
  clearChatPaidSlots(chatId);
30405
+ clearUsage(chatId);
29414
30406
  setSessionStartedAt(chatId);
29415
30407
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "Session cleared (no summary)", detail: { field: "session", action: "clear" } });
29416
30408
  await channel.sendText(chatId, "\u{1F9FD} Session cleared. No summary saved.", { parseMode: "plain", priority: 0 /* P0_CRITICAL */ });
@@ -29487,6 +30479,17 @@ async function handleSummarizeCommand(chatId, commandArgs, msg, channel) {
29487
30479
  }
29488
30480
  }
29489
30481
  }
30482
+ function formatSummarizerLabel(backend2, model2, fallbackModel) {
30483
+ if (backend2 === "off") return "Off";
30484
+ if (backend2) return `${backend2}: ${model2 ?? "default"}`;
30485
+ return `Auto (${fallbackModel ?? "default"})`;
30486
+ }
30487
+ function summarizerStatusLine(chatId, adapter) {
30488
+ const { config: config2, source } = getSummarizerWithSource(chatId);
30489
+ const label2 = formatSummarizerLabel(config2.backend, config2.model, adapter?.summarizerModel);
30490
+ if (source === "auto") return label2.toLowerCase();
30491
+ return `${label2} (${source === "global" ? "global" : "per-chat"})`;
30492
+ }
29490
30493
  async function handleStatusCommand(chatId, commandArgs, msg, channel) {
29491
30494
  const sessionId = getSessionId(chatId);
29492
30495
  const cwd = getCwd(chatId);
@@ -29503,12 +30506,21 @@ async function handleStatusCommand(chatId, commandArgs, msg, channel) {
29503
30506
  const thinking2 = getThinkingLevel(chatId);
29504
30507
  const mode = getMode(chatId);
29505
30508
  const modelSig = getModelSignature(chatId);
29506
- const contextMax = adapter?.contextWindow[model2] ?? 2e5;
29507
- const contextUsed = usage2.context_size;
29508
- const contextPct = contextMax > 0 ? contextUsed / contextMax * 100 : 0;
29509
- const ctxBar = buildBar(contextPct);
29510
- const usedK = (contextUsed / 1e3).toFixed(1);
29511
- const maxK = (contextMax / 1e3).toFixed(0);
30509
+ let contextLine;
30510
+ if (adapter?.type === "api") {
30511
+ const { estimateContextUsage: estimateContextUsage2 } = await Promise.resolve().then(() => (init_api_context(), api_context_exports));
30512
+ const contextMax = adapter.contextWindow[model2] ?? 8192;
30513
+ const ctxEst = estimateContextUsage2(chatId, contextMax);
30514
+ const ctxBar = buildBar(ctxEst.percentage);
30515
+ const usedK = (ctxEst.estimatedTokens / 1e3).toFixed(1);
30516
+ const maxK = (contextMax / 1e3).toFixed(0);
30517
+ contextLine = `\u{1F4D0} Context: ${ctxBar} ${usedK}K/${maxK}K (${ctxEst.percentage.toFixed(1)}%) \xB7 compacts at 85%`;
30518
+ } else {
30519
+ const pairCount = getMessagePairCount(chatId);
30520
+ const threshold = 30;
30521
+ const remaining = Math.max(0, threshold - pairCount);
30522
+ contextLine = `\u{1F4D0} Session: ${pairCount}/${threshold} messages \xB7 compacts in ${remaining}`;
30523
+ }
29512
30524
  const bootRow = getDb().prepare("SELECT value FROM meta WHERE key = 'boot_time'").get();
29513
30525
  let uptimeStr = "unknown";
29514
30526
  if (bootRow) {
@@ -29562,20 +30574,26 @@ async function handleStatusCommand(chatId, commandArgs, msg, channel) {
29562
30574
  const iDeep = iStats.agentic;
29563
30575
  const intentLine = iTotal > 0 ? `\u26A1 ${iTotal} messages: ${iQuick} quick, ${iDeep} deep` : `\u26A1 No messages classified yet`;
29564
30576
  const sqCount = getActiveSideQuestCount(chatId);
30577
+ const { getPinnedSkills: getPinnedSkills2 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
30578
+ const pinnedSkills = getPinnedSkills2(chatId);
30579
+ const pinnedLine = pinnedSkills.length > 0 ? `\u{1F4CC} Pinned: ${pinnedSkills.join(", ")}` : `\u{1F4CC} Pinned: none`;
29565
30580
  const lines = [
29566
30581
  `\u{1F43E} CC-Claw v${VERSION}`,
29567
30582
  `\u23F1 Uptime: ${uptimeStr}`,
30583
+ isChatBusy(chatId) ? `\u{1F534} Agent: Busy` : `\u{1F7E2} Agent: Idle`,
29568
30584
  ``,
29569
30585
  buildSectionHeader("Engine"),
29570
30586
  `\u{1F9E0} ${adapter?.displayName ?? backendId ?? "not set"} \xB7 ${formatModelShort(model2)}${slotInfo}`,
29571
30587
  `\u{1F4AD} Think: ${thinking2} \xB7 Mode: ${mode}`,
29572
30588
  `\u{1F916} Agents: ${getAgentMode(chatId)}`,
30589
+ pinnedLine,
29573
30590
  `\u{1F507} Voice: ${voice2 ? "on" : "off"} \xB7 Sig: ${modelSig}`,
30591
+ `\u{1F4DD} Summarizer: ${summarizerStatusLine(chatId, adapter)}`,
29574
30592
  ``,
29575
30593
  buildSectionHeader("Session"),
29576
30594
  `\u{1F4CB} ${sessionId ?? "no active session"}`,
29577
30595
  `\u{1F4C1} ${cwd ?? "default workspace"}`,
29578
- `\u{1F4D0} Context: ${ctxBar} ${usedK}K/${maxK}K (${contextPct.toFixed(1)}%)`,
30596
+ contextLine,
29579
30597
  ...sqCount > 0 ? [`\u{1F5FA} Side quests: ${sqCount} active`] : [],
29580
30598
  ``,
29581
30599
  buildSectionHeader("Usage"),
@@ -29614,11 +30632,16 @@ async function handleBackendCommand2(chatId, commandArgs, msg, channel) {
29614
30632
  async function handleBackendShortcutCommand(chatId, commandArgs, msg, channel) {
29615
30633
  const command = msg.command;
29616
30634
  const backendId = command;
29617
- if (getAllBackendIds().includes(backendId)) {
29618
- await sendBackendSwitchConfirmation(chatId, backendId, channel);
29619
- } else {
30635
+ if (!getAllBackendIds().includes(backendId)) {
29620
30636
  await channel.sendText(chatId, `Backend "${command}" is not available.`, { parseMode: "plain" });
30637
+ return;
29621
30638
  }
30639
+ if (getDisabledBackends().includes(backendId)) {
30640
+ const { sendBackendConfigPanel: sendBackendConfigPanel2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
30641
+ await sendBackendConfigPanel2(chatId, backendId, channel);
30642
+ return;
30643
+ }
30644
+ await sendBackendSwitchConfirmation(chatId, backendId, channel);
29622
30645
  }
29623
30646
  async function handleModelCommand(chatId, commandArgs, msg, channel) {
29624
30647
  await sendModelKeyboard(chatId, channel);
@@ -29986,8 +31009,17 @@ async function handleSummarizerCommand(chatId, commandArgs, msg, channel) {
29986
31009
  } catch {
29987
31010
  adapter = null;
29988
31011
  }
29989
- const current = getSummarizer(chatId);
29990
- const currentLabel = current.backend === "off" ? "Off" : current.backend ? `${current.backend}:${current.model ?? "default"}` : `Auto (${adapter?.summarizerModel ?? "default"})`;
31012
+ const { config: current, source, globalConfig } = getSummarizerWithSource(chatId);
31013
+ const overrideCount = countSummarizerOverrides();
31014
+ const currentLabel = formatSummarizerLabel(current.backend, current.model, adapter?.summarizerModel);
31015
+ const sourceTag = source === "per-chat" ? "per-chat override" : source === "global" ? "global default" : "auto";
31016
+ const headerLines = [`Summarizer: ${currentLabel} (${sourceTag})`];
31017
+ if (globalConfig.backend) {
31018
+ headerLines.push(`Global default: ${formatSummarizerLabel(globalConfig.backend, globalConfig.model)}`);
31019
+ }
31020
+ if (overrideCount > 0) {
31021
+ headerLines.push(`${overrideCount} chat${overrideCount === 1 ? "" : "s"} with per-chat overrides`);
31022
+ }
29991
31023
  if (typeof channel.sendKeyboard === "function") {
29992
31024
  const isAuto = !current.backend && current.backend !== "off";
29993
31025
  const isOff = current.backend === "off";
@@ -30035,7 +31067,14 @@ async function handleSummarizerCommand(chatId, commandArgs, msg, channel) {
30035
31067
  }]);
30036
31068
  }
30037
31069
  }
30038
- await channel.sendKeyboard(chatId, `Session summarizer (current: ${currentLabel}):`, buttons);
31070
+ if (overrideCount > 0) {
31071
+ buttons.push([{
31072
+ label: `Clear All Overrides (${overrideCount})`,
31073
+ data: "summarizer:clearall",
31074
+ style: "danger"
31075
+ }]);
31076
+ }
31077
+ await channel.sendKeyboard(chatId, headerLines.join("\n"), buttons);
30039
31078
  } else {
30040
31079
  await channel.sendText(chatId, `Summarizer: ${currentLabel}
30041
31080
 
@@ -30752,7 +31791,7 @@ __export(import_exports, {
30752
31791
  parseClaudeConfig: () => parseClaudeConfig,
30753
31792
  parseCursorConfig: () => parseCursorConfig
30754
31793
  });
30755
- import { readFileSync as readFileSync20 } from "fs";
31794
+ import { readFileSync as readFileSync21 } from "fs";
30756
31795
  import { join as join31 } from "path";
30757
31796
  import { homedir as homedir10 } from "os";
30758
31797
  function parseClaudeConfig(config2) {
@@ -30769,7 +31808,7 @@ async function discoverFromSource(source) {
30769
31808
  case "claude": {
30770
31809
  const path = join31(homedir10(), ".claude", "settings.json");
30771
31810
  try {
30772
- return parseClaudeConfig(JSON.parse(readFileSync20(path, "utf-8")));
31811
+ return parseClaudeConfig(JSON.parse(readFileSync21(path, "utf-8")));
30773
31812
  } catch {
30774
31813
  warn(`[mcp-import] Could not read ${path}`);
30775
31814
  return [];
@@ -30778,7 +31817,7 @@ async function discoverFromSource(source) {
30778
31817
  case "cursor": {
30779
31818
  const path = join31(homedir10(), ".cursor", "mcp.json");
30780
31819
  try {
30781
- return parseCursorConfig(JSON.parse(readFileSync20(path, "utf-8")));
31820
+ return parseCursorConfig(JSON.parse(readFileSync21(path, "utf-8")));
30782
31821
  } catch {
30783
31822
  warn(`[mcp-import] Could not read ${path}`);
30784
31823
  return [];
@@ -31442,6 +32481,10 @@ async function handleCallback(chatId, data, channel, messageId) {
31442
32481
  if (data.startsWith("backend:")) {
31443
32482
  const chosen = data.slice(8);
31444
32483
  if (!getAllBackendIds().includes(chosen)) return;
32484
+ if (getDisabledBackends().includes(chosen)) {
32485
+ await sendBackendConfigPanel(chatId, chosen, channel, messageId, false);
32486
+ return;
32487
+ }
31445
32488
  const previous = getBackend(chatId);
31446
32489
  if (chosen === previous) {
31447
32490
  await sendBackendConfigPanel(chatId, chosen, channel, messageId, false);
@@ -31495,6 +32538,32 @@ async function handleCallback(chatId, data, channel, messageId) {
31495
32538
  }
31496
32539
  }
31497
32540
  await sendBackendConfigPanel(chatId, backendId, channel, messageId, false);
32541
+ } else if (rest.startsWith("disable:")) {
32542
+ const backendId = rest.slice(8);
32543
+ const { sendBackendDisableFlow: sendBackendDisableFlow2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
32544
+ await sendBackendDisableFlow2(chatId, backendId, channel, messageId);
32545
+ } else if (rest.startsWith("enable:")) {
32546
+ const backendId = rest.slice(7);
32547
+ setBackendDisabled(backendId, false);
32548
+ await sendBackendConfigPanel(chatId, backendId, channel, messageId, false);
32549
+ } else if (rest.startsWith("reassign:")) {
32550
+ const parts = rest.slice(9).split(":");
32551
+ const disablingBackend = parts[0];
32552
+ const targetBackend = parts[1];
32553
+ const current = getBackend(chatId);
32554
+ const { executeBackendDisable: executeBackendDisable2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
32555
+ await executeBackendDisable2(chatId, disablingBackend, channel, messageId, {
32556
+ reassignTo: targetBackend,
32557
+ ...current === disablingBackend ? { switchTo: targetBackend } : {}
32558
+ });
32559
+ } else if (rest.startsWith("disableswitch:")) {
32560
+ const parts = rest.slice(14).split(":");
32561
+ const disablingBackend = parts[0];
32562
+ const switchToBackend = parts[1];
32563
+ const { executeBackendDisable: executeBackendDisable2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
32564
+ await executeBackendDisable2(chatId, disablingBackend, channel, messageId, {
32565
+ switchTo: switchToBackend
32566
+ });
31498
32567
  }
31499
32568
  } else if (data.startsWith("backend_confirm_clear:")) {
31500
32569
  const target = data.slice("backend_confirm_clear:".length);
@@ -31558,14 +32627,40 @@ ${value ? "Full tool inputs/results will be saved to ~/.cc-claw/logs/sessions/"
31558
32627
  if (rest === "auto") {
31559
32628
  clearSummarizer(chatId);
31560
32629
  await channel.sendText(chatId, "Summarizer set to auto (uses active backend).", { parseMode: "plain" });
31561
- } else if (rest === "off") {
31562
- setSummarizer(chatId, "off", null);
31563
- await channel.sendText(chatId, "Session summarization disabled.", { parseMode: "plain" });
32630
+ } else if (rest === "clearall") {
32631
+ const cleared = clearAllSummarizerOverrides();
32632
+ await channel.sendText(chatId, `Cleared ${cleared} per-chat override${cleared === 1 ? "" : "s"}. All chats now use the global default.`, { parseMode: "plain" });
32633
+ } else if (rest.startsWith("promote:")) {
32634
+ const promoteValue = rest.slice(8);
32635
+ const db3 = getDb();
32636
+ const promote = db3.transaction(() => {
32637
+ if (promoteValue === "off") {
32638
+ setGlobalSummarizer("off", null);
32639
+ } else {
32640
+ const [bk, ...modelParts] = promoteValue.split(":");
32641
+ setGlobalSummarizer(bk, modelParts.join(":") || null);
32642
+ }
32643
+ return clearAllSummarizerOverrides();
32644
+ });
32645
+ const cleared = promote();
32646
+ const label2 = promoteValue === "off" ? "Off" : promoteValue;
32647
+ const clearedNote = cleared > 0 ? ` Cleared ${cleared} per-chat override${cleared === 1 ? "" : "s"}.` : "";
32648
+ await channel.sendText(chatId, `Global summarizer set to ${label2}.${clearedNote} All chats now use this default.`, { parseMode: "plain" });
31564
32649
  } else {
31565
- const [bk, ...modelParts] = rest.split(":");
31566
- const mdl = modelParts.join(":") || null;
32650
+ const isOff = rest === "off";
32651
+ const bk = isOff ? "off" : rest.split(":")[0];
32652
+ const mdl = isOff ? null : rest.split(":").slice(1).join(":") || null;
31567
32653
  setSummarizer(chatId, bk, mdl);
31568
- await channel.sendText(chatId, `Summarizer pinned to ${bk}:${mdl ?? "default"}.`, { parseMode: "plain" });
32654
+ const displayLabel = isOff ? "Off" : `${bk}:${mdl ?? "default"}`;
32655
+ const confirmMsg = isOff ? "Session summarization disabled for this chat." : `Summarizer pinned to ${displayLabel} for this chat.`;
32656
+ const promoteData = `summarizer:promote:${rest}`;
32657
+ if (typeof channel.sendKeyboard === "function") {
32658
+ await channel.sendKeyboard(chatId, confirmMsg, [
32659
+ [{ label: "Set as Global Default", data: promoteData }]
32660
+ ]);
32661
+ } else {
32662
+ await channel.sendText(chatId, confirmMsg, { parseMode: "plain" });
32663
+ }
31569
32664
  }
31570
32665
  } else if (data.startsWith("perms:")) {
31571
32666
  let chosen = data.slice(6);
@@ -33309,10 +34404,10 @@ ${formatValidationReport2(validation)}` : "\n\n\u2705 Quality checks passed.";
33309
34404
  return;
33310
34405
  }
33311
34406
  try {
33312
- const { readFileSync: readFileSync35, writeFileSync: writeFileSync16 } = await import("fs");
34407
+ const { readFileSync: readFileSync36, writeFileSync: writeFileSync16 } = await import("fs");
33313
34408
  const { updateFrontmatter: updateFrontmatter2, ensureThreeTierFrontmatter: ensureThreeTierFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
33314
34409
  const { invalidateSkillCache: invalidateSkillCache2 } = await Promise.resolve().then(() => (init_discover(), discover_exports));
33315
- const raw = readFileSync35(skill.filePath, "utf-8");
34410
+ const raw = readFileSync36(skill.filePath, "utf-8");
33316
34411
  let updated = ensureThreeTierFrontmatter2(raw, { name: skillName, source: "cc-claw" });
33317
34412
  updated = updateFrontmatter2(updated, { status: "approved" });
33318
34413
  writeFileSync16(skill.filePath, updated, "utf-8");
@@ -33362,14 +34457,14 @@ ${stillUnapproved.length} more skill${stillUnapproved.length !== 1 ? "s" : ""} t
33362
34457
  return;
33363
34458
  }
33364
34459
  try {
33365
- const { readFileSync: readFileSync35, writeFileSync: writeFileSync16 } = await import("fs");
34460
+ const { readFileSync: readFileSync36, writeFileSync: writeFileSync16 } = await import("fs");
33366
34461
  const { updateFrontmatter: updateFrontmatter2, ensureThreeTierFrontmatter: ensureThreeTierFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
33367
34462
  const { invalidateSkillCache: invalidateSkillCache2 } = await Promise.resolve().then(() => (init_discover(), discover_exports));
33368
34463
  let approvedCount = 0;
33369
34464
  const issues = [];
33370
34465
  for (const skill of unapproved) {
33371
34466
  try {
33372
- const raw = readFileSync35(skill.filePath, "utf-8");
34467
+ const raw = readFileSync36(skill.filePath, "utf-8");
33373
34468
  let updated = ensureThreeTierFrontmatter2(raw, { name: skill.name, source: "cc-claw" });
33374
34469
  updated = updateFrontmatter2(updated, { status: "approved" });
33375
34470
  writeFileSync16(skill.filePath, updated, "utf-8");
@@ -33403,6 +34498,29 @@ ${issues.join("\n")}`;
33403
34498
  );
33404
34499
  }
33405
34500
  return;
34501
+ } else if (data.startsWith("pin:")) {
34502
+ const skillName = data.slice(4);
34503
+ const { getPinnedSkills: getPinnedSkills2, setPinnedSkills: setPinnedSkills2 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
34504
+ const pinned = getPinnedSkills2(chatId);
34505
+ if (pinned.includes(skillName)) {
34506
+ setPinnedSkills2(chatId, pinned.filter((n) => n !== skillName));
34507
+ await sendOrEditKeyboard(
34508
+ chatId,
34509
+ channel,
34510
+ messageId,
34511
+ `\u{1F4CC} Unpinned "${skillName}" from this chat.`,
34512
+ [[{ label: "\u2190 Skills", data: "skills:page:1" }]]
34513
+ );
34514
+ } else {
34515
+ setPinnedSkills2(chatId, [...pinned, skillName]);
34516
+ await sendOrEditKeyboard(
34517
+ chatId,
34518
+ channel,
34519
+ messageId,
34520
+ `\u{1F4CC} Pinned "${skillName}" to this chat. It will always inject regardless of message content.`,
34521
+ [[{ label: "\u2190 Skills", data: "skills:page:1" }]]
34522
+ );
34523
+ }
33406
34524
  } else if (data.startsWith("skill:")) {
33407
34525
  const parts = data.slice(6).split(":");
33408
34526
  let skillName;
@@ -33610,134 +34728,6 @@ var init_thread_wrapper = __esm({
33610
34728
  }
33611
34729
  });
33612
34730
 
33613
- // src/intent/complexity.ts
33614
- var complexity_exports = {};
33615
- __export(complexity_exports, {
33616
- classifyComplexity: () => classifyComplexity
33617
- });
33618
- function wordCount(text) {
33619
- return text.trim().split(/\s+/).filter(Boolean).length;
33620
- }
33621
- function classifyComplexity(text) {
33622
- const trimmed = text.trim();
33623
- const lower = trimmed.toLowerCase();
33624
- const words = wordCount(trimmed);
33625
- if (TRIVIAL_EXACT.has(lower)) {
33626
- log(`[complexity] "${lower}" -> trivial (exact match)`);
33627
- return "trivial";
33628
- }
33629
- if (trimmed.length <= 4 && /^[\p{Emoji}\s]+$/u.test(trimmed)) {
33630
- log(`[complexity] "${trimmed}" -> trivial (emoji-only)`);
33631
- return "trivial";
33632
- }
33633
- if (COMPLEX_SIGNALS.test(trimmed) && (words > 15 || trimmed.length > 200)) {
33634
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> complex (signal words + length)`);
33635
- return "complex";
33636
- }
33637
- if (MUTATION_VERBS.test(trimmed)) {
33638
- const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
33639
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (mutation verb)`);
33640
- return tier;
33641
- }
33642
- for (const pattern of CODE_PATTERNS) {
33643
- if (pattern.test(trimmed)) {
33644
- const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
33645
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (code pattern)`);
33646
- return tier;
33647
- }
33648
- }
33649
- if (trimmed.length > 200 || words > 25) {
33650
- const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
33651
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (long message)`);
33652
- return tier;
33653
- }
33654
- for (const pattern of QUESTION_PATTERNS) {
33655
- if (pattern.test(trimmed)) {
33656
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> simple (question)`);
33657
- return "simple";
33658
- }
33659
- }
33660
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> moderate (default)`);
33661
- return "moderate";
33662
- }
33663
- var TRIVIAL_EXACT, COMPLEX_SIGNALS, MUTATION_VERBS, CODE_PATTERNS, QUESTION_PATTERNS;
33664
- var init_complexity = __esm({
33665
- "src/intent/complexity.ts"() {
33666
- "use strict";
33667
- init_log();
33668
- TRIVIAL_EXACT = /* @__PURE__ */ new Set([
33669
- "hey",
33670
- "hi",
33671
- "hello",
33672
- "yo",
33673
- "sup",
33674
- "howdy",
33675
- "hiya",
33676
- "thanks",
33677
- "thank you",
33678
- "thx",
33679
- "ty",
33680
- "thank u",
33681
- "ok",
33682
- "okay",
33683
- "k",
33684
- "kk",
33685
- "cool",
33686
- "nice",
33687
- "great",
33688
- "awesome",
33689
- "perfect",
33690
- "good morning",
33691
- "good night",
33692
- "good evening",
33693
- "gm",
33694
- "gn",
33695
- "bye",
33696
- "goodbye",
33697
- "later",
33698
- "see ya",
33699
- "cya",
33700
- "lol",
33701
- "lmao",
33702
- "haha",
33703
- "heh",
33704
- "np",
33705
- "no problem",
33706
- "no worries",
33707
- "nw",
33708
- "got it",
33709
- "understood",
33710
- "roger",
33711
- "copy",
33712
- "good",
33713
- "fine",
33714
- "alright",
33715
- "sure",
33716
- "yes",
33717
- "no",
33718
- "yep",
33719
- "nope",
33720
- "yeah",
33721
- "nah"
33722
- ]);
33723
- COMPLEX_SIGNALS = /\b(architect|design|research|plan|strategy|migration|microservice|distributed|end-to-end|trade-offs|pros\s+and\s+cons)\b/i;
33724
- MUTATION_VERBS = /\b(fix|create|build|deploy|refactor|implement|write|add|update|edit|test|debug)\b/i;
33725
- CODE_PATTERNS = [
33726
- /```/,
33727
- // code blocks
33728
- /\.[a-z]{1,5}\b/,
33729
- // file extensions (.ts, .py, .json)
33730
- /[/\\][\w.-]+/
33731
- // file paths (/src/foo, .\bar)
33732
- ];
33733
- QUESTION_PATTERNS = [
33734
- /^(?:what|which|where|when|why|how)\b/i,
33735
- /^(?:show|tell|list|explain)\b/i,
33736
- /^(?:is|are)\b/i
33737
- ];
33738
- }
33739
- });
33740
-
33741
34731
  // src/intent/auto-route.ts
33742
34732
  var auto_route_exports = {};
33743
34733
  __export(auto_route_exports, {
@@ -34564,6 +35554,8 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34564
35554
  return;
34565
35555
  }
34566
35556
  getTypingManager().acquire(chatId, channel);
35557
+ let stopDraftTimer = () => {
35558
+ };
34567
35559
  try {
34568
35560
  const tMode = settings.getMode();
34569
35561
  const tVerbose = settings.getVerboseLevel();
@@ -34623,7 +35615,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34623
35615
  draftState.dirty = true;
34624
35616
  };
34625
35617
  }
34626
- const stopDraftTimer2 = () => {
35618
+ stopDraftTimer = () => {
34627
35619
  if (draftState?.flushTimer) {
34628
35620
  clearInterval(draftState.flushTimer);
34629
35621
  draftState.flushTimer = null;
@@ -34659,9 +35651,14 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34659
35651
  } catch {
34660
35652
  }
34661
35653
  },
34662
- onCompaction: (cid) => {
34663
- channel.sendText(cid, "\u{1F4BE} Context saved to memory.").catch(() => {
34664
- });
35654
+ onCompaction: (cid, phase) => {
35655
+ if (phase === "triggered") {
35656
+ channel.sendText(cid, "\u{1F4BE} Context compaction triggered \u2014 saving conversation to memory...").catch(() => {
35657
+ });
35658
+ } else {
35659
+ channel.sendText(cid, "\u{1F4BE} Context saved to memory.").catch(() => {
35660
+ });
35661
+ }
34665
35662
  },
34666
35663
  onSlotRotation: (cid, from, to) => {
34667
35664
  const slots = getGeminiSlots();
@@ -34679,7 +35676,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
34679
35676
  });
34680
35677
  }
34681
35678
  });
34682
- stopDraftTimer2();
35679
+ stopDraftTimer();
34683
35680
  const elapsedMs = Date.now() - sigT0;
34684
35681
  const elapsedSec = (elapsedMs / 1e3).toFixed(1);
34685
35682
  if (liveStatus && response.thinkingText?.trim()) {
@@ -35081,6 +36078,12 @@ async function executeJob(job) {
35081
36078
  log(`[scheduler] Running job #${job.id}: ${job.description} (model=${resolvedModel})`);
35082
36079
  try {
35083
36080
  const backendId = resolveJobBackendId(job);
36081
+ if (getDisabledBackends().includes(backendId)) {
36082
+ const msg = `Backend "${backendId}" is disabled. Reassign this job with /editjob or re-enable the backend.`;
36083
+ completeJobRun(runId, "skipped", { error: msg, durationMs: Date.now() - t0 });
36084
+ await notifyJobFailure(job, msg);
36085
+ return;
36086
+ }
35084
36087
  const limitMsg = checkBackendLimits(backendId);
35085
36088
  if (limitMsg) {
35086
36089
  completeJobRun(runId, "skipped", { error: limitMsg, durationMs: Date.now() - t0 });
@@ -35511,7 +36514,7 @@ var init_wrap_backend = __esm({
35511
36514
  });
35512
36515
 
35513
36516
  // src/agents/runners/config-loader.ts
35514
- import { readFileSync as readFileSync21, readdirSync as readdirSync14, existsSync as existsSync30, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
36517
+ import { readFileSync as readFileSync22, readdirSync as readdirSync14, existsSync as existsSync30, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
35515
36518
  import { join as join33 } from "path";
35516
36519
  import { execFileSync as execFileSync3 } from "child_process";
35517
36520
  function resolveExecutable2(config2) {
@@ -35653,7 +36656,7 @@ function configToRunner(config2) {
35653
36656
  }
35654
36657
  function loadRunnerConfig(filePath) {
35655
36658
  try {
35656
- const content = readFileSync21(filePath, "utf-8");
36659
+ const content = readFileSync22(filePath, "utf-8");
35657
36660
  return JSON.parse(content);
35658
36661
  } catch (err) {
35659
36662
  warn(`[runners] Failed to load config ${filePath}: ${err}`);
@@ -36269,7 +37272,7 @@ var init_telegram2 = __esm({
36269
37272
  };
36270
37273
  }
36271
37274
  async start(handler) {
36272
- await this.bot.api.setMyCommands([
37275
+ await this.throttle.send(this.primaryChatId, "setMyCommands", () => this.bot.api.setMyCommands([
36273
37276
  // Core
36274
37277
  { command: "menu", description: "Home screen \u2014 quick-access keyboard" },
36275
37278
  { command: "m", description: "Home screen (alias for /menu)" },
@@ -36347,7 +37350,7 @@ var init_telegram2 = __esm({
36347
37350
  // Context & info
36348
37351
  { command: "info", description: "Current chat context (ID, topic, sender, settings)" },
36349
37352
  { command: "council", description: "Multi-model debate (select models, anonymous rounds)" }
36350
- ]);
37353
+ ]));
36351
37354
  this.bot.on("message", async (ctx) => {
36352
37355
  const chatId = ctx.chat.id.toString();
36353
37356
  const senderId = ctx.from?.id?.toString() ?? "";
@@ -36393,15 +37396,13 @@ var init_telegram2 = __esm({
36393
37396
  const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
36394
37397
  log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
36395
37398
  if (!this.isAuthorized(userId) && !this.isAuthorized(chatId)) {
36396
- ctx.answerCallbackQuery("Unauthorized").catch(() => {
36397
- });
37399
+ this.throttle.tryBestEffort(chatId, "answerCallbackQuery:unauth", () => ctx.answerCallbackQuery("Unauthorized"));
36398
37400
  return;
36399
37401
  }
36400
37402
  const data = ctx.callbackQuery.data;
36401
37403
  const messageId = ctx.callbackQuery.message?.message_id?.toString();
36402
37404
  const threadId = ctx.callbackQuery.message?.message_thread_id;
36403
- ctx.answerCallbackQuery().catch(() => {
36404
- });
37405
+ this.throttle.tryBestEffort(chatId, "answerCallbackQuery", () => ctx.answerCallbackQuery());
36405
37406
  (async () => {
36406
37407
  let ch = this;
36407
37408
  if (threadId) {
@@ -36450,7 +37451,7 @@ var init_telegram2 = __esm({
36450
37451
  this.keepaliveInterval = setInterval(async () => {
36451
37452
  if (!this.pollingExpected) return;
36452
37453
  try {
36453
- await this.bot.api.getMe();
37454
+ await this.throttle.tryBestEffort(this.primaryChatId, "getMe:keepalive", () => this.bot.api.getMe());
36454
37455
  this.lastPollingCheckAt = Date.now();
36455
37456
  } catch (err) {
36456
37457
  error("[telegram] Keepalive ping failed:", err);
@@ -36590,7 +37591,7 @@ var init_telegram2 = __esm({
36590
37591
  );
36591
37592
  }
36592
37593
  async downloadFile(fileId) {
36593
- const file = await this.bot.api.getFile(fileId);
37594
+ const file = await this.throttle.send(this.primaryChatId, "getFile", () => this.bot.api.getFile(fileId));
36594
37595
  const fileUrl = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${file.file_path}`;
36595
37596
  const response = await fetch(fileUrl);
36596
37597
  return Buffer.from(await response.arrayBuffer());
@@ -36879,7 +37880,11 @@ var init_telegram2 = __esm({
36879
37880
  }
36880
37881
  }
36881
37882
  }
36882
- await ctx.answerInlineQuery(results.slice(0, 10), { cache_time: 30 });
37883
+ await this.throttle.tryBestEffort(
37884
+ this.primaryChatId,
37885
+ "answerInlineQuery",
37886
+ () => ctx.answerInlineQuery(results.slice(0, 10), { cache_time: 30 })
37887
+ );
36883
37888
  }
36884
37889
  trackAgentMessage(messageId, chatId) {
36885
37890
  if (this.agentMessageIds.size >= 1e4) {
@@ -37066,7 +38071,7 @@ var init_telegram2 = __esm({
37066
38071
  });
37067
38072
 
37068
38073
  // src/skills/bootstrap.ts
37069
- import { existsSync as existsSync31, readFileSync as readFileSync22, writeFileSync as writeFileSync10, copyFileSync as copyFileSync3, readdirSync as readdirSync15 } from "fs";
38074
+ import { existsSync as existsSync31, readFileSync as readFileSync23, writeFileSync as writeFileSync10, copyFileSync as copyFileSync3, readdirSync as readdirSync15 } from "fs";
37070
38075
  import { readdir as readdir6, copyFile } from "fs/promises";
37071
38076
  import { join as join34, dirname as dirname6 } from "path";
37072
38077
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -37115,7 +38120,7 @@ function migrateSkillsToThreeTier() {
37115
38120
  }
37116
38121
  if (!skillPath) continue;
37117
38122
  try {
37118
- const raw = readFileSync22(skillPath, "utf-8");
38123
+ const raw = readFileSync23(skillPath, "utf-8");
37119
38124
  const fmMatch = raw.match(/^---\s*\n([\s\S]*?)\n---/);
37120
38125
  if (fmMatch) {
37121
38126
  const fm = fmMatch[1];
@@ -37758,7 +38763,7 @@ var index_exports = {};
37758
38763
  __export(index_exports, {
37759
38764
  main: () => main
37760
38765
  });
37761
- import { mkdirSync as mkdirSync12, existsSync as existsSync33, renameSync as renameSync2, statSync as statSync9, readFileSync as readFileSync24 } from "fs";
38766
+ import { mkdirSync as mkdirSync12, existsSync as existsSync33, renameSync as renameSync2, statSync as statSync9, readFileSync as readFileSync25 } from "fs";
37762
38767
  import { join as join36 } from "path";
37763
38768
  import dotenv from "dotenv";
37764
38769
  function migrateLayout() {
@@ -37801,7 +38806,7 @@ async function main() {
37801
38806
  let version = "unknown";
37802
38807
  try {
37803
38808
  const pkgPath = new URL("../package.json", import.meta.url);
37804
- version = JSON.parse(readFileSync24(pkgPath, "utf-8")).version;
38809
+ version = JSON.parse(readFileSync25(pkgPath, "utf-8")).version;
37805
38810
  } catch {
37806
38811
  }
37807
38812
  log(`[cc-claw] Starting v${version}`);
@@ -38029,6 +39034,11 @@ ${lines.join("\n")}`;
38029
39034
  log("[cc-claw] Ready!");
38030
39035
  const shutdown = async (signal) => {
38031
39036
  log(`[cc-claw] Received ${signal}, shutting down...`);
39037
+ const forceExit = setTimeout(() => {
39038
+ error("[cc-claw] Shutdown timed out after 30s \u2014 forcing exit.");
39039
+ process.exit(1);
39040
+ }, 3e4);
39041
+ forceExit.unref();
38032
39042
  try {
38033
39043
  const { stopAllActiveAgents: stopAllActiveAgents2, stopStaleChatSweep: stopStaleChatSweep2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
38034
39044
  stopAllActiveAgents2();
@@ -38540,13 +39550,13 @@ var init_daemon = __esm({
38540
39550
  });
38541
39551
 
38542
39552
  // src/cli/resolve-chat.ts
38543
- import { readFileSync as readFileSync26 } from "fs";
39553
+ import { readFileSync as readFileSync27 } from "fs";
38544
39554
  function resolveChatId2(globalOpts) {
38545
39555
  const explicit = globalOpts.chat;
38546
39556
  if (explicit) return explicit;
38547
39557
  if (_cachedDefault) return _cachedDefault;
38548
39558
  try {
38549
- const content = readFileSync26(ENV_PATH, "utf-8");
39559
+ const content = readFileSync27(ENV_PATH, "utf-8");
38550
39560
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
38551
39561
  if (match) {
38552
39562
  _cachedDefault = match[1].split(",")[0].trim();
@@ -39008,7 +40018,7 @@ var logs_exports = {};
39008
40018
  __export(logs_exports, {
39009
40019
  logsCommand: () => logsCommand
39010
40020
  });
39011
- import { existsSync as existsSync37, readFileSync as readFileSync28, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
40021
+ import { existsSync as existsSync37, readFileSync as readFileSync29, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
39012
40022
  async function logsCommand(opts) {
39013
40023
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
39014
40024
  if (!existsSync37(logFile)) {
@@ -39016,7 +40026,7 @@ async function logsCommand(opts) {
39016
40026
  process.exit(1);
39017
40027
  }
39018
40028
  const maxLines = parseInt(opts.lines ?? "100", 10);
39019
- const content = readFileSync28(logFile, "utf-8");
40029
+ const content = readFileSync29(logFile, "utf-8");
39020
40030
  const allLines = content.split("\n");
39021
40031
  const tailLines = allLines.slice(-maxLines);
39022
40032
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
@@ -39027,7 +40037,7 @@ async function logsCommand(opts) {
39027
40037
  let lastLength = content.length;
39028
40038
  watchFile2(logFile, { interval: 500 }, () => {
39029
40039
  try {
39030
- const newContent = readFileSync28(logFile, "utf-8");
40040
+ const newContent = readFileSync29(logFile, "utf-8");
39031
40041
  if (newContent.length > lastLength) {
39032
40042
  const newPart = newContent.slice(lastLength);
39033
40043
  process.stdout.write(newPart);
@@ -39059,7 +40069,7 @@ __export(session_logs_exports, {
39059
40069
  sessionLogsList: () => sessionLogsList,
39060
40070
  sessionLogsTail: () => sessionLogsTail
39061
40071
  });
39062
- import { readFileSync as readFileSync29, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
40072
+ import { readFileSync as readFileSync30, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
39063
40073
  async function sessionLogsList(opts) {
39064
40074
  const logs = listSessionLogs();
39065
40075
  if (logs.length === 0) {
@@ -39117,12 +40127,12 @@ async function sessionLogsTail(opts) {
39117
40127
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
39118
40128
  let lastLength = 0;
39119
40129
  try {
39120
- lastLength = readFileSync29(targetPath, "utf-8").length;
40130
+ lastLength = readFileSync30(targetPath, "utf-8").length;
39121
40131
  } catch {
39122
40132
  }
39123
40133
  watchFile3(targetPath, { interval: 500 }, () => {
39124
40134
  try {
39125
- const content = readFileSync29(targetPath, "utf-8");
40135
+ const content = readFileSync30(targetPath, "utf-8");
39126
40136
  if (content.length > lastLength) {
39127
40137
  process.stdout.write(content.slice(lastLength));
39128
40138
  lastLength = content.length;
@@ -39166,7 +40176,7 @@ __export(gemini_exports, {
39166
40176
  geminiReorder: () => geminiReorder,
39167
40177
  geminiRotation: () => geminiRotation
39168
40178
  });
39169
- import { existsSync as existsSync39, mkdirSync as mkdirSync14, writeFileSync as writeFileSync13, readFileSync as readFileSync30, chmodSync } from "fs";
40179
+ import { existsSync as existsSync39, mkdirSync as mkdirSync14, writeFileSync as writeFileSync13, readFileSync as readFileSync31, chmodSync } from "fs";
39170
40180
  import { join as join38 } from "path";
39171
40181
  import { createInterface as createInterface8 } from "readline";
39172
40182
  function requireDb() {
@@ -39197,7 +40207,7 @@ function resolveOAuthEmail(configHome) {
39197
40207
  try {
39198
40208
  const accountsPath = join38(configHome, ".gemini", "google_accounts.json");
39199
40209
  if (!existsSync39(accountsPath)) return null;
39200
- const accounts = JSON.parse(readFileSync30(accountsPath, "utf-8"));
40210
+ const accounts = JSON.parse(readFileSync31(accountsPath, "utf-8"));
39201
40211
  return accounts.active || null;
39202
40212
  } catch {
39203
40213
  return null;
@@ -39460,7 +40470,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
39460
40470
  setGeminiSlotEnabled2(slotId, true);
39461
40471
  let accountEmail = slot.label;
39462
40472
  try {
39463
- const accounts = JSON.parse(readFileSync30(join38(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
40473
+ const accounts = JSON.parse(readFileSync31(join38(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
39464
40474
  if (accounts.active) accountEmail = accounts.active;
39465
40475
  } catch {
39466
40476
  }
@@ -39519,7 +40529,7 @@ __export(backend_cmd_factory_exports, {
39519
40529
  makeReorder: () => makeReorder,
39520
40530
  registerBackendSlotCommands: () => registerBackendSlotCommands
39521
40531
  });
39522
- import { existsSync as existsSync40, mkdirSync as mkdirSync15, readFileSync as readFileSync31 } from "fs";
40532
+ import { existsSync as existsSync40, mkdirSync as mkdirSync15, readFileSync as readFileSync32 } from "fs";
39523
40533
  import { join as join39 } from "path";
39524
40534
  import { createInterface as createInterface9 } from "readline";
39525
40535
  function requireDb2() {
@@ -39873,7 +40883,7 @@ var init_backend_cmd_factory = __esm({
39873
40883
  const claudeJsonNested = join39(slotDir, ".claude", ".claude.json");
39874
40884
  if (existsSync40(claudeJson)) {
39875
40885
  try {
39876
- const data = JSON.parse(readFileSync31(claudeJson, "utf-8"));
40886
+ const data = JSON.parse(readFileSync32(claudeJson, "utf-8"));
39877
40887
  return Boolean(data.oauthAccount);
39878
40888
  } catch {
39879
40889
  return false;
@@ -39881,7 +40891,7 @@ var init_backend_cmd_factory = __esm({
39881
40891
  }
39882
40892
  if (existsSync40(claudeJsonNested)) {
39883
40893
  try {
39884
- const data = JSON.parse(readFileSync31(claudeJsonNested, "utf-8"));
40894
+ const data = JSON.parse(readFileSync32(claudeJsonNested, "utf-8"));
39885
40895
  return Boolean(data.oauthAccount);
39886
40896
  } catch {
39887
40897
  return false;
@@ -39904,7 +40914,7 @@ var init_backend_cmd_factory = __esm({
39904
40914
  try {
39905
40915
  const claudeJson = join39(slotDir, ".claude.json");
39906
40916
  if (existsSync40(claudeJson)) {
39907
- const data = JSON.parse(readFileSync31(claudeJson, "utf-8"));
40917
+ const data = JSON.parse(readFileSync32(claudeJson, "utf-8"));
39908
40918
  if (data.oauthAccount?.emailAddress) return data.oauthAccount.emailAddress;
39909
40919
  }
39910
40920
  } catch {
@@ -39923,7 +40933,7 @@ var init_backend_cmd_factory = __esm({
39923
40933
  },
39924
40934
  extractLabel: (slotDir) => {
39925
40935
  try {
39926
- const authData = JSON.parse(readFileSync31(join39(slotDir, "auth.json"), "utf-8"));
40936
+ const authData = JSON.parse(readFileSync32(join39(slotDir, "auth.json"), "utf-8"));
39927
40937
  if (authData.email) return authData.email;
39928
40938
  if (authData.account_name) return authData.account_name;
39929
40939
  if (authData.user?.email) return authData.user.email;
@@ -41290,7 +42300,7 @@ __export(config_exports2, {
41290
42300
  configList: () => configList,
41291
42301
  configSet: () => configSet
41292
42302
  });
41293
- import { existsSync as existsSync49, readFileSync as readFileSync32 } from "fs";
42303
+ import { existsSync as existsSync49, readFileSync as readFileSync33 } from "fs";
41294
42304
  async function configList(globalOpts) {
41295
42305
  if (!existsSync49(DB_PATH)) {
41296
42306
  outputError("DB_NOT_FOUND", "Database not found.");
@@ -41376,7 +42386,7 @@ async function configEnv(_globalOpts) {
41376
42386
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
41377
42387
  process.exit(1);
41378
42388
  }
41379
- const content = readFileSync32(ENV_PATH, "utf-8");
42389
+ const content = readFileSync33(ENV_PATH, "utf-8");
41380
42390
  const entries = {};
41381
42391
  const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
41382
42392
  for (const line of content.split("\n")) {
@@ -42372,11 +43382,11 @@ __export(chat_exports2, {
42372
43382
  chatSend: () => chatSend
42373
43383
  });
42374
43384
  import { request as httpRequest2 } from "http";
42375
- import { readFileSync as readFileSync33, existsSync as existsSync59 } from "fs";
43385
+ import { readFileSync as readFileSync34, existsSync as existsSync59 } from "fs";
42376
43386
  function getToken2() {
42377
43387
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
42378
43388
  try {
42379
- if (existsSync59(TOKEN_PATH)) return readFileSync33(TOKEN_PATH, "utf-8").trim();
43389
+ if (existsSync59(TOKEN_PATH)) return readFileSync34(TOKEN_PATH, "utf-8").trim();
42380
43390
  } catch {
42381
43391
  }
42382
43392
  return null;
@@ -43533,7 +44543,7 @@ var init_optimize3 = __esm({
43533
44543
 
43534
44544
  // src/setup.ts
43535
44545
  var setup_exports = {};
43536
- import { existsSync as existsSync61, writeFileSync as writeFileSync15, readFileSync as readFileSync34, copyFileSync as copyFileSync5, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
44546
+ import { existsSync as existsSync61, writeFileSync as writeFileSync15, readFileSync as readFileSync35, copyFileSync as copyFileSync5, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
43537
44547
  import { execFileSync as execFileSync6 } from "child_process";
43538
44548
  import { createInterface as createInterface11 } from "readline";
43539
44549
  import { join as join41 } from "path";
@@ -43625,7 +44635,7 @@ async function setup() {
43625
44635
  if (envSource) {
43626
44636
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
43627
44637
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
43628
- const existing = readFileSync34(envSource, "utf-8");
44638
+ const existing = readFileSync35(envSource, "utf-8");
43629
44639
  for (const line of existing.split("\n")) {
43630
44640
  const match = line.match(/^([^#=]+)=(.*)$/);
43631
44641
  if (match) env[match[1].trim()] = match[2].trim();