cc-claw 0.29.3 → 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 +1221 -389
  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.3" : (() => {
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
 
@@ -2819,6 +2920,7 @@ __export(chat_settings_exports, {
2819
2920
  clearExecMode: () => clearExecMode,
2820
2921
  clearModel: () => clearModel,
2821
2922
  clearModelMap: () => clearModelMap,
2923
+ clearPinnedSkills: () => clearPinnedSkills,
2822
2924
  clearSummarizer: () => clearSummarizer,
2823
2925
  clearThinkingLevel: () => clearThinkingLevel,
2824
2926
  countSummarizerOverrides: () => countSummarizerOverrides,
@@ -2837,6 +2939,7 @@ __export(chat_settings_exports, {
2837
2939
  getMode: () => getMode,
2838
2940
  getModel: () => getModel,
2839
2941
  getPendingEscalation: () => getPendingEscalation,
2942
+ getPinnedSkills: () => getPinnedSkills,
2840
2943
  getRecentBookmarks: () => getRecentBookmarks,
2841
2944
  getSessionLogEnabled: () => getSessionLogEnabled,
2842
2945
  getShowThinkingUi: () => getShowThinkingUi,
@@ -2856,6 +2959,7 @@ __export(chat_settings_exports, {
2856
2959
  setGlobalSummarizer: () => setGlobalSummarizer,
2857
2960
  setMode: () => setMode,
2858
2961
  setModel: () => setModel,
2962
+ setPinnedSkills: () => setPinnedSkills,
2859
2963
  setSessionLogEnabled: () => setSessionLogEnabled,
2860
2964
  setShowThinkingUi: () => setShowThinkingUi,
2861
2965
  setSkillSuggestionsEnabled: () => setSkillSuggestionsEnabled,
@@ -3265,6 +3369,27 @@ function toggleSessionLogEnabled(chatId) {
3265
3369
  setSessionLogEnabled(chatId, next);
3266
3370
  return next;
3267
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
+ }
3268
3393
  function getAllowPaidSlots(chatId, backend2) {
3269
3394
  const row = getDb().prepare(
3270
3395
  "SELECT 1 FROM chat_allow_paid_slots WHERE chat_id = ? AND backend = ?"
@@ -3648,6 +3773,7 @@ __export(jobs_exports, {
3648
3773
  cancelJobById: () => cancelJobById,
3649
3774
  completeJobRun: () => completeJobRun,
3650
3775
  getActiveJobs: () => getActiveJobs,
3776
+ getActiveJobsByBackend: () => getActiveJobsByBackend,
3651
3777
  getAllJobs: () => getAllJobs,
3652
3778
  getJobById: () => getJobById,
3653
3779
  getJobRuns: () => getJobRuns,
@@ -3655,6 +3781,7 @@ __export(jobs_exports, {
3655
3781
  insertJob: () => insertJob,
3656
3782
  insertJobRun: () => insertJobRun,
3657
3783
  pruneJobRuns: () => pruneJobRuns,
3784
+ reassignJobsBackend: () => reassignJobsBackend,
3658
3785
  resetJobFailures: () => resetJobFailures,
3659
3786
  updateJob: () => updateJob,
3660
3787
  updateJobEnabled: () => updateJobEnabled,
@@ -3711,6 +3838,15 @@ function getActiveJobs() {
3711
3838
  function getAllJobs() {
3712
3839
  return getDb().prepare(`${JOB_SELECT} WHERE active = 1 ORDER BY id`).all().map((r) => mapJobRow(r));
3713
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
+ }
3714
3850
  function updateJobEnabled(id, enabled) {
3715
3851
  const result = getDb().prepare("UPDATE jobs SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, id);
3716
3852
  return result.changes > 0;
@@ -4904,6 +5040,7 @@ __export(store_exports5, {
4904
5040
  clearMessageLog: () => clearMessageLog,
4905
5041
  clearModel: () => clearModel,
4906
5042
  clearModelMap: () => clearModelMap,
5043
+ clearPinnedSkills: () => clearPinnedSkills,
4907
5044
  clearSession: () => clearSession,
4908
5045
  clearSummarizer: () => clearSummarizer,
4909
5046
  clearThinkingLevel: () => clearThinkingLevel,
@@ -4922,6 +5059,7 @@ __export(store_exports5, {
4922
5059
  forget: () => forget,
4923
5060
  forgetMemory: () => forgetMemory,
4924
5061
  getActiveJobs: () => getActiveJobs,
5062
+ getActiveJobsByBackend: () => getActiveJobsByBackend,
4925
5063
  getActiveWatches: () => getActiveWatches,
4926
5064
  getAgentMode: () => getAgentMode,
4927
5065
  getAllBackendLimits: () => getAllBackendLimits,
@@ -4948,6 +5086,7 @@ __export(store_exports5, {
4948
5086
  getChatUsageByModel: () => getChatUsageByModel,
4949
5087
  getCwd: () => getCwd,
4950
5088
  getDb: () => getDb,
5089
+ getDisabledBackends: () => getDisabledBackends,
4951
5090
  getEligibleBackendSlots: () => getEligibleBackendSlots,
4952
5091
  getEligibleGeminiSlots: () => getEligibleGeminiSlots,
4953
5092
  getEnabledTools: () => getEnabledTools,
@@ -4969,6 +5108,7 @@ __export(store_exports5, {
4969
5108
  getNextBackendSlot: () => getNextBackendSlot,
4970
5109
  getNextGeminiSlot: () => getNextGeminiSlot,
4971
5110
  getPendingEscalation: () => getPendingEscalation,
5111
+ getPinnedSkills: () => getPinnedSkills,
4972
5112
  getRecentBookmarks: () => getRecentBookmarks,
4973
5113
  getRecentMemories: () => getRecentMemories,
4974
5114
  getRecentMessageLog: () => getRecentMessageLog,
@@ -5010,6 +5150,7 @@ __export(store_exports5, {
5010
5150
  pruneJobRuns: () => pruneJobRuns,
5011
5151
  pruneMessageLog: () => pruneMessageLog,
5012
5152
  queueHalfLifeExtension: () => queueHalfLifeExtension,
5153
+ reassignJobsBackend: () => reassignJobsBackend,
5013
5154
  recall: () => recall,
5014
5155
  reenableBackendSlot: () => reenableBackendSlot,
5015
5156
  reenableSlot: () => reenableSlot,
@@ -5041,6 +5182,7 @@ __export(store_exports5, {
5041
5182
  setAgentMode: () => setAgentMode,
5042
5183
  setAllowPaidSlots: () => setAllowPaidSlots,
5043
5184
  setBackend: () => setBackend,
5185
+ setBackendDisabled: () => setBackendDisabled,
5044
5186
  setBackendLimit: () => setBackendLimit,
5045
5187
  setBackendRotationMode: () => setBackendRotationMode,
5046
5188
  setBackendSlotEnabled: () => setBackendSlotEnabled,
@@ -5056,6 +5198,7 @@ __export(store_exports5, {
5056
5198
  setMode: () => setMode,
5057
5199
  setModel: () => setModel,
5058
5200
  setModelSignature: () => setModelSignature,
5201
+ setPinnedSkills: () => setPinnedSkills,
5059
5202
  setResponseStyle: () => setResponseStyle,
5060
5203
  setSessionId: () => setSessionId,
5061
5204
  setSessionLogEnabled: () => setSessionLogEnabled,
@@ -5087,6 +5230,7 @@ function initDatabase() {
5087
5230
  db = new Database(DB_PATH);
5088
5231
  db.pragma("journal_mode = WAL");
5089
5232
  db.pragma("foreign_keys = ON");
5233
+ _setSchemaDbGetter(() => db);
5090
5234
  initSchema(db);
5091
5235
  startMaintenanceTimers(db);
5092
5236
  }
@@ -5102,6 +5246,7 @@ function getDb() {
5102
5246
  function _setDbForTesting(testDb) {
5103
5247
  db = testDb;
5104
5248
  _testDbPath = testDb.name;
5249
+ _setSchemaDbGetter(() => db);
5105
5250
  }
5106
5251
  var db, _testDbPath;
5107
5252
  var init_store5 = __esm({
@@ -7618,6 +7763,52 @@ var init_html = __esm({
7618
7763
  }
7619
7764
  });
7620
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
+
7621
7812
  // src/channels/telegram-throttle.ts
7622
7813
  var telegram_throttle_exports = {};
7623
7814
  __export(telegram_throttle_exports, {
@@ -7645,6 +7836,7 @@ var init_telegram_throttle = __esm({
7645
7836
  "src/channels/telegram-throttle.ts"() {
7646
7837
  "use strict";
7647
7838
  init_log();
7839
+ init_format_time();
7648
7840
  PER_DM_INTERVAL_MS = 1e3;
7649
7841
  PER_GROUP_INTERVAL_MS = 3500;
7650
7842
  P0_PACING_MS = 150;
@@ -7826,7 +8018,7 @@ var init_telegram_throttle = __esm({
7826
8018
  while (this.queue.length > 0) {
7827
8019
  while (this.isPaused()) {
7828
8020
  if (this.pauseStartedAt > 0 && Date.now() - this.pauseStartedAt > MAX_TOTAL_PAUSE_MS) {
7829
- warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items (pause remains until ${new Date(this.pausedUntil).toISOString()})`);
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())})`);
7830
8022
  this.flushQueueWithError("Telegram rate limit exceeded max wait time");
7831
8023
  break;
7832
8024
  }
@@ -8669,6 +8861,70 @@ function buildHandoffContract() {
8669
8861
  "**Open questions:** Anything unresolved or needing the main agent's attention."
8670
8862
  ].join("\n");
8671
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
+ }
8672
8928
  var REAL_HOME, TASK_WORKER_SKILL_BUDGET;
8673
8929
  var init_shared = __esm({
8674
8930
  "src/bootstrap/templates/shared.ts"() {
@@ -8684,6 +8940,7 @@ var discover_exports = {};
8684
8940
  __export(discover_exports, {
8685
8941
  discoverAllSkills: () => discoverAllSkills,
8686
8942
  invalidateSkillCache: () => invalidateSkillCache,
8943
+ parseFrontmatter: () => parseFrontmatter,
8687
8944
  resolveSkillForTask: () => resolveSkillForTask,
8688
8945
  stripFrontmatter: () => stripFrontmatter
8689
8946
  });
@@ -8756,7 +9013,8 @@ async function scanSkillDir(skillsDir, source) {
8756
9013
  contentHash: hash,
8757
9014
  type: frontmatter.type,
8758
9015
  status: frontmatter.status,
8759
- requires: frontmatter.requires
9016
+ requires: frontmatter.requires,
9017
+ triggers: frontmatter.triggers
8760
9018
  });
8761
9019
  }
8762
9020
  return results;
@@ -8788,7 +9046,8 @@ function mergeAndDeduplicate(raw) {
8788
9046
  contentHash: primary.contentHash,
8789
9047
  type: primary.type,
8790
9048
  status: primary.status,
8791
- requires: primary.requires
9049
+ requires: primary.requires,
9050
+ triggers: primary.triggers
8792
9051
  });
8793
9052
  } else {
8794
9053
  for (const [hash, copies] of byHash) {
@@ -8803,7 +9062,8 @@ function mergeAndDeduplicate(raw) {
8803
9062
  contentHash: hash,
8804
9063
  type: primary.type,
8805
9064
  status: primary.status,
8806
- requires: primary.requires
9065
+ requires: primary.requires,
9066
+ triggers: primary.triggers
8807
9067
  });
8808
9068
  }
8809
9069
  }
@@ -8823,7 +9083,8 @@ function parseFrontmatter(content, fallbackName, source) {
8823
9083
  description: "",
8824
9084
  type: "tool",
8825
9085
  status: source === "cc-claw" ? "imported" : "external",
8826
- requires: []
9086
+ requires: [],
9087
+ triggers: []
8827
9088
  };
8828
9089
  }
8829
9090
  const fm = fmMatch[1];
@@ -8849,12 +9110,21 @@ function parseFrontmatter(content, fallbackName, source) {
8849
9110
  if (cleaned2) requires.push(cleaned2);
8850
9111
  }
8851
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
+ }
8852
9121
  return {
8853
9122
  name: nameMatch?.[1] ? strip(nameMatch[1]) : fallbackName,
8854
9123
  description: descMatch?.[1] ? strip(descMatch[1]) : "",
8855
9124
  type,
8856
9125
  status,
8857
- requires
9126
+ requires,
9127
+ triggers
8858
9128
  };
8859
9129
  }
8860
9130
  function stripFrontmatter(content) {
@@ -8865,9 +9135,9 @@ function resolveSkillForTask(skillName) {
8865
9135
  for (const candidate of SKILL_FILE_CANDIDATES3) {
8866
9136
  const skillPath = join9(SKILLS_PATH, skillName, candidate);
8867
9137
  try {
8868
- const { readFileSync: readFileSync35, existsSync: existsSync62 } = __require("fs");
9138
+ const { readFileSync: readFileSync36, existsSync: existsSync62 } = __require("fs");
8869
9139
  if (!existsSync62(skillPath)) continue;
8870
- const raw = readFileSync35(skillPath, "utf-8");
9140
+ const raw = readFileSync36(skillPath, "utf-8");
8871
9141
  const fm = parseFrontmatter(raw, skillName, "cc-claw");
8872
9142
  if (fm.status !== "approved") {
8873
9143
  log(`[skills] Skill "${skillName}" has status "${fm.status}" \u2014 only approved skills can be used as task worker identity`);
@@ -11330,6 +11600,12 @@ ${buildPlatformQuickReference()}
11330
11600
 
11331
11601
  ${buildToolUsageRules()}
11332
11602
 
11603
+ ## Agentic Discipline
11604
+ ${buildAgenticCore()}
11605
+
11606
+ ## Follow-Through
11607
+ ${buildFollowThrough()}
11608
+
11333
11609
  ## CC-Claw Constraints
11334
11610
  ${buildDatabaseSafetyBoundary()}
11335
11611
  - Do not invent library APIs or documentation you are not certain exists.
@@ -11361,28 +11637,13 @@ ${user}
11361
11637
  You are an autonomous agent \u2014 keep going until the user's request is completely
11362
11638
  resolved before ending your turn and yielding back to the user.
11363
11639
 
11364
- PERSISTENCE RULES:
11365
- - Persist through the full lifecycle: gather context \u2192 plan \u2192 implement \u2192 verify
11366
- \u2192 report. Do not stop at analysis, partial fixes, or drafts.
11367
- - Do not output a plan, status update, or progress summary as your final message.
11368
- If there is more work to do, do the work. A status message without a following
11369
- tool call will terminate your turn prematurely.
11370
- - Only terminate your turn when you are sure the problem is fully solved.
11371
-
11372
- TOOL PERSISTENCE:
11373
- - Do not stop early when another tool call would materially improve correctness
11374
- or completeness.
11375
- - Keep calling tools until: (1) the task is complete, and (2) verification passes.
11376
- - If a tool returns empty, errors, or partial results \u2014 retry with a different
11377
- strategy. Tool errors are not stop conditions.
11378
-
11379
- BIAS TO ACTION:
11380
- - Default to implementing with reasonable assumptions rather than asking
11381
- clarifying questions, unless truly blocked on a detail that would change
11382
- correctness or safety, or involves an irreversible action.
11383
- - Never stop after completing only part of the request.
11640
+ ${buildFollowThrough()}
11384
11641
  </default_follow_through_policy>
11385
11642
 
11643
+ <agentic_framework>
11644
+ ${buildAgenticCore()}
11645
+ </agentic_framework>
11646
+
11386
11647
  <output_contract>
11387
11648
  - Simple answers: 1-4 sentences in Telegram HTML, no preamble
11388
11649
  - Code tasks: show the change, explain what changed and why in 1-2 sentences
@@ -11447,18 +11708,15 @@ ${soul}
11447
11708
  ${user}
11448
11709
  </user_profile>
11449
11710
 
11450
- <instructions>
11451
- 1. Implement rather than suggest unless the request is clearly read-only.
11452
- 2. Read relevant files before answering questions about code.
11453
- 3. Keep casual responses concise. Reserve detail for research, code, and complex tasks.
11454
- 4. Confirm before destructive actions (file deletion, force operations, external sends).
11455
- 5. Use cc-claw CLI or daemon HTTP API for state. Run \`cc-claw --ai\` when unsure.
11456
- 6. Verify paths before acting. User paths are often approximate.
11457
- </instructions>
11711
+ <agentic_framework>
11712
+ ${buildAgenticCore()}
11713
+ </agentic_framework>
11714
+
11715
+ <follow_through_policy>
11716
+ ${buildFollowThrough()}
11717
+ </follow_through_policy>
11458
11718
 
11459
11719
  <tool_guidance>
11460
- Call multiple tools in parallel when they have no dependencies.
11461
- Only make changes directly requested. Do not add features or refactor beyond what was asked.
11462
11720
  ${buildToolUsageRules()}
11463
11721
  </tool_guidance>
11464
11722
 
@@ -11486,6 +11744,138 @@ var init_cursor2 = __esm({
11486
11744
  }
11487
11745
  });
11488
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
+
11489
11879
  // src/bootstrap/templates/claude-task.ts
11490
11880
  function buildClaudeTaskTemplate() {
11491
11881
  const date = getCurrentDate();
@@ -11544,26 +11934,7 @@ Your identity and expertise are defined by the skill injected into your prompt.
11544
11934
  You are an autonomous agent \u2014 keep going until the task is completely resolved
11545
11935
  before ending your turn and yielding back to the user.
11546
11936
 
11547
- PERSISTENCE RULES:
11548
- - Persist through the full lifecycle: gather context \u2192 plan \u2192 implement \u2192 verify
11549
- \u2192 report. Do not stop at analysis, partial fixes, or drafts.
11550
- - Do not output a plan, status update, or progress summary as your final message.
11551
- If there is more work to do, do the work. A status message without a following
11552
- tool call will terminate your turn prematurely.
11553
- - Only terminate your turn when you are sure the task is fully solved.
11554
-
11555
- TOOL PERSISTENCE:
11556
- - Do not stop early when another tool call would materially improve correctness
11557
- or completeness.
11558
- - Keep calling tools until: (1) the task is complete, and (2) verification passes.
11559
- - If a tool returns empty, errors, or partial results \u2014 retry with a different
11560
- strategy. Tool errors are not stop conditions.
11561
-
11562
- BIAS TO ACTION:
11563
- - Default to implementing with reasonable assumptions rather than asking
11564
- clarifying questions, unless truly blocked on a detail that would change
11565
- correctness or safety.
11566
- - Never stop after completing only part of the task.
11937
+ ${buildFollowThrough()}
11567
11938
  </default_follow_through_policy>
11568
11939
 
11569
11940
  <operating_rules>
@@ -11727,6 +12098,10 @@ function syncNativeCliFiles() {
11727
12098
  writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-codex.md"), codexContent, "utf-8");
11728
12099
  writeFileSync6(join14(WORKSPACE_PATH, "AGENTS.md"), codexContent, "utf-8");
11729
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");
11730
12105
  const fallbackContent = [
11731
12106
  "# CC-Claw System Instructions",
11732
12107
  "",
@@ -11749,7 +12124,7 @@ function syncNativeCliFiles() {
11749
12124
  const codexTaskContent = buildCodexTaskTemplate();
11750
12125
  writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-claude-task.md"), claudeTaskContent, "utf-8");
11751
12126
  writeFileSync6(join14(IDENTITY_PATH, "CC-CLAW-codex-task.md"), codexTaskContent, "utf-8");
11752
- 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");
11753
12128
  }
11754
12129
  var SOUL_PATH, USER_PATH, CONTEXT_DIR, LEGACY_SOUL_PATH, LEGACY_USER_PATH, LEGACY_CLAUDE_MD, LEGACY_GEMINI_MD;
11755
12130
  var init_init = __esm({
@@ -11761,6 +12136,8 @@ var init_init = __esm({
11761
12136
  init_gemini2();
11762
12137
  init_codex2();
11763
12138
  init_cursor2();
12139
+ init_api();
12140
+ init_api_task();
11764
12141
  init_claude_task();
11765
12142
  init_codex_task();
11766
12143
  init_shared();
@@ -11775,19 +12152,333 @@ var init_init = __esm({
11775
12152
  }
11776
12153
  });
11777
12154
 
11778
- // src/bootstrap/loader.ts
11779
- import { readFileSync as readFileSync10, readdirSync as readdirSync6 } from "fs";
11780
- import { join as join15 } from "path";
11781
- function tokenize(text) {
11782
- return new Set(
11783
- text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 3)
11784
- );
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;
11785
12162
  }
11786
- function stripFrontmatter3(content) {
11787
- if (!content.startsWith("---")) return content;
11788
- const end = content.indexOf("---", 3);
11789
- return end === -1 ? content : content.slice(end + 3).trim();
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;
11790
12292
  }
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");
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";
11791
12482
  function loadMarkdownDir(dir, cacheRef, wordSource = (c) => c) {
11792
12483
  if (cacheRef.v && Date.now() - cacheRef.v.timestamp < CACHE_TTL_MS4) {
11793
12484
  return cacheRef.v.files;
@@ -11797,8 +12488,8 @@ function loadMarkdownDir(dir, cacheRef, wordSource = (c) => c) {
11797
12488
  for (const entry of readdirSync6(dir)) {
11798
12489
  if (!entry.endsWith(".md")) continue;
11799
12490
  try {
11800
- const content = readFileSync10(join15(dir, entry), "utf-8").trim();
11801
- 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)) });
11802
12493
  } catch {
11803
12494
  continue;
11804
12495
  }
@@ -11809,7 +12500,7 @@ function loadMarkdownDir(dir, cacheRef, wordSource = (c) => c) {
11809
12500
  return files;
11810
12501
  }
11811
12502
  function findBestMatch(userMessage, files, opts) {
11812
- const msgWords = tokenize(userMessage);
12503
+ const msgWords = tokenize2(userMessage);
11813
12504
  if (msgWords.size === 0) return null;
11814
12505
  let best = null;
11815
12506
  for (const entry of files.values()) {
@@ -11832,16 +12523,6 @@ function searchContext(userMessage) {
11832
12523
  { maxChars: MAX_CONTEXT_CHARS }
11833
12524
  );
11834
12525
  }
11835
- function searchSkill(userMessage) {
11836
- return findBestMatch(
11837
- userMessage,
11838
- loadMarkdownDir(SKILLS_DIR, skillCacheRef, stripFrontmatter3),
11839
- {
11840
- maxChars: MAX_SKILL_CHARS,
11841
- filter: ({ content }) => content.includes("status: approved") || content.includes("status: imported")
11842
- }
11843
- );
11844
- }
11845
12526
  async function assembleBootstrapPrompt(userMessage, entityType = "main", profile = "interactive", chatId, permMode, responseStyle, agentMode, sideQuestContext, planningDirective, chatContext, backendType) {
11846
12527
  const sections = [];
11847
12528
  if (planningDirective) {
@@ -11855,15 +12536,24 @@ async function assembleBootstrapPrompt(userMessage, entityType = "main", profile
11855
12536
  sections.push(buildPermissionNotice(permMode));
11856
12537
  }
11857
12538
  if (backendType === "api") {
11858
- 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]
11859
12553
  You are operating as a direct API model (not a CLI like Claude Code or Gemini CLI).
11860
- External tools (gsearch, pwm, gws, gemcli, nlm, curl, python3, etc.) are accessed ONLY via the \`restrictedBash\` tool.
11861
- NEVER call external CLIs as direct tools by name \u2014 they are not registered as native tools.
11862
- Correct: restrictedBash({"command": "gsearch \\"query\\" --type news"})
11863
- Incorrect: gsearch({"query": "..."}) \u2190 this will fail silently
11864
- If a skill or instruction says to use a CLI tool, always route it through restrictedBash.
11865
-
12554
+ External tools are accessed ONLY via the \`restrictedBash\` tool. NEVER call external CLIs as direct tool names.
11866
12555
  ${buildCliEnvironment()}`);
12556
+ }
11867
12557
  }
11868
12558
  if (responseStyle) {
11869
12559
  if (responseStyle === "concise") {
@@ -11893,10 +12583,23 @@ ${ctx}`);
11893
12583
  }
11894
12584
  }
11895
12585
  if (profile === "interactive" || profile === "chat") {
11896
- const skill = searchSkill(userMessage);
11897
- if (skill) {
11898
- sections.push(`[Relevant skill]
11899
- ${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}`);
11900
12603
  }
11901
12604
  }
11902
12605
  if (chatId && profile !== "minimal" && profile !== "chat") {
@@ -12064,7 +12767,7 @@ ${parts.join("\n")}${MEMORY_DISCIPLINE}`;
12064
12767
  }
12065
12768
  return null;
12066
12769
  }
12067
- 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;
12068
12771
  var init_loader2 = __esm({
12069
12772
  "src/bootstrap/loader.ts"() {
12070
12773
  "use strict";
@@ -12077,14 +12780,22 @@ var init_loader2 = __esm({
12077
12780
  init_store5();
12078
12781
  init_store();
12079
12782
  init_backends();
12783
+ init_inject2();
12784
+ init_discover();
12785
+ init_chat_settings();
12786
+ init_text_utils();
12080
12787
  lastSyncMs = 0;
12788
+ apiHarnessCache = {
12789
+ main: null,
12790
+ task: null,
12791
+ cachedMs: 0
12792
+ };
12081
12793
  CONTEXT_DIR2 = join15(WORKSPACE_PATH, "context");
12082
12794
  SKILLS_DIR = join15(WORKSPACE_PATH, "skills");
12083
12795
  MAX_CONTEXT_CHARS = 4e3;
12084
- MAX_SKILL_CHARS = 6e3;
12085
12796
  CACHE_TTL_MS4 = 3e4;
12797
+ tokenize2 = (text) => tokenizeText(text, 4);
12086
12798
  contextCacheRef = { v: null };
12087
- skillCacheRef = { v: null };
12088
12799
  ACTIVITY_TOKEN_BUDGET = 1500;
12089
12800
  INBOX_TOKEN_BUDGET = 2e3;
12090
12801
  WHITEBOARD_TOKEN_BUDGET = 500;
@@ -12119,7 +12830,8 @@ __export(client_exports, {
12119
12830
  listModels: () => listModels,
12120
12831
  ping: () => ping,
12121
12832
  runningModels: () => runningModels,
12122
- showModel: () => showModel
12833
+ showModel: () => showModel,
12834
+ unloadModel: () => unloadModel
12123
12835
  });
12124
12836
  function buildHeaders(apiKey) {
12125
12837
  const headers = {
@@ -12205,6 +12917,30 @@ async function deleteModel(baseUrl, modelName, opts) {
12205
12917
  clearTimeout(timeout);
12206
12918
  }
12207
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
+ }
12208
12944
  async function listModels(baseUrl, opts) {
12209
12945
  const res = await fetch(`${baseUrl}/api/tags`, {
12210
12946
  signal: opts?.signal,
@@ -12892,7 +13628,9 @@ __export(service_exports, {
12892
13628
  isAnyServerOnline: () => isAnyServerOnline,
12893
13629
  listServers: () => listServers2,
12894
13630
  removeServer: () => removeServer2,
12895
- runQualityGateTest: () => runQualityGateTest
13631
+ runQualityGateTest: () => runQualityGateTest,
13632
+ tryUnloadModel: () => tryUnloadModel,
13633
+ unloadModel: () => unloadModel2
12896
13634
  });
12897
13635
  function addServer2(name, host, port = 11434, apiKey) {
12898
13636
  const existing = getServer(name);
@@ -13082,6 +13820,17 @@ function getServerForModel(modelName) {
13082
13820
  apiKey: server.apiKey
13083
13821
  };
13084
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
+ }
13085
13834
  function extractContextWindow(show) {
13086
13835
  const info = show.model_info ?? {};
13087
13836
  for (const key of Object.keys(info)) {
@@ -13880,6 +14629,7 @@ function classifyReaction(emoji) {
13880
14629
  return null;
13881
14630
  }
13882
14631
  async function detectAndLogSignals(chatId, userMessage, agentResponse, ctx = {}) {
14632
+ const injectedSkills = getAndClearInjectedSkills(chatId);
13883
14633
  const db3 = getDb();
13884
14634
  const status = getReflectionStatus(db3, chatId);
13885
14635
  if (status === "frozen") return;
@@ -13887,18 +14637,29 @@ async function detectAndLogSignals(chatId, userMessage, agentResponse, ctx = {})
13887
14637
  if (recentCount >= RATE_LIMIT_MAX_SIGNALS) return;
13888
14638
  const signals = classifySignals(userMessage, agentResponse);
13889
14639
  const context = agentResponse.slice(0, 500) || null;
14640
+ const uniqueByType = /* @__PURE__ */ new Map();
13890
14641
  for (const signal of signals) {
13891
- logSignal(db3, {
13892
- chatId,
13893
- signalType: signal.signalType,
13894
- trigger: signal.trigger,
13895
- context,
13896
- source: signal.source,
13897
- confidence: signal.confidence,
13898
- backend: ctx.backendId ?? null,
13899
- model: ctx.model ?? null,
13900
- thinkingLevel: ctx.thinkingLevel ?? null
13901
- });
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
+ }
13902
14663
  }
13903
14664
  }
13904
14665
  function logReactionSignal(chatId, emoji) {
@@ -13941,6 +14702,7 @@ var init_detect = __esm({
13941
14702
  "use strict";
13942
14703
  init_store4();
13943
14704
  init_store5();
14705
+ init_inject2();
13944
14706
  CORRECTION_PATTERNS = [
13945
14707
  // Audit O36: Tightened — require "no" followed by correction context (not "No problem", "No worries")
13946
14708
  /^no[,.\s!]+\s*(that'?s|it'?s|i\s|you|this|the|not|don'?t|stop|wrong)/i,
@@ -14019,9 +14781,11 @@ function killProcessGroup(proc, signal = "SIGTERM") {
14019
14781
  function runCompaction(chatId, reason, onCompaction) {
14020
14782
  return summarizeWithFallbackChain(chatId).then((saved) => {
14021
14783
  if (saved) {
14022
- clearSession(chatId);
14023
- clearUsage(chatId);
14024
- onCompaction?.(chatId);
14784
+ return withChatLock(chatId, async () => {
14785
+ clearSession(chatId);
14786
+ clearUsage(chatId);
14787
+ onCompaction?.(chatId);
14788
+ });
14025
14789
  }
14026
14790
  }).catch((err) => {
14027
14791
  warn(`[agent] Compaction failed for ${chatId} (${reason}): ${err}`);
@@ -14084,6 +14848,13 @@ function stopAgent(chatId) {
14084
14848
  if (state.process) killProcessGroup(state.process, "SIGKILL");
14085
14849
  }, 2e3);
14086
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
+ }
14087
14858
  return true;
14088
14859
  }
14089
14860
  function getGeminiFallback(model2) {
@@ -14095,7 +14866,8 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14095
14866
  const thinkingConfig = thinkingLevel && thinkingLevel !== "auto" ? adapter.applyThinkingConfig(thinkingLevel, model2) : void 0;
14096
14867
  const env = opts?.envOverride ? thinkingConfig?.envOverrides ? { ...opts.envOverride, ...thinkingConfig.envOverrides } : opts.envOverride : adapter.getEnv(thinkingConfig?.envOverrides);
14097
14868
  const finalArgs = thinkingConfig?.extraArgs ? [...config2.args, ...thinkingConfig.extraArgs] : config2.args;
14098
- 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)"}`);
14099
14871
  const stdinMode = adapter.id === "codex" ? "pipe" : "ignore";
14100
14872
  const proc = spawn4(config2.executable, finalArgs, {
14101
14873
  env,
@@ -14204,7 +14976,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
14204
14976
  for (const ev of events) {
14205
14977
  switch (ev.type) {
14206
14978
  case "init":
14207
- log(`[agent] Session init at ${elapsed()}`);
14979
+ log(`[agent] Session init at ${elapsed()} sessionId=${ev.sessionId ?? "(none)"}`);
14208
14980
  if (ev.sessionId) sessionId = ev.sessionId;
14209
14981
  resetContentSilenceTimer();
14210
14982
  break;
@@ -14401,6 +15173,7 @@ Partial output: ${accumulatedText.slice(-500)}`;
14401
15173
  return;
14402
15174
  }
14403
15175
  const cleanedResult = stripThinkingContent(resultText || accumulatedText);
15176
+ log(`[agent:spawn] Resolving: sessionId=${sessionId ?? "(none)"} hasText=${!!cleanedResult} code=${code}`);
14404
15177
  resolve3({ resultText: cleanedResult, thinkingText: accumulatedThinking, sessionId, input, output: output2, cacheRead, contextSize: null, sawToolEvents, sawResultEvent });
14405
15178
  });
14406
15179
  });
@@ -14609,14 +15382,14 @@ async function askAgentImpl(chatId, userMessage, opts) {
14609
15382
  const responseStyle = getResponseStyle(settingsChat);
14610
15383
  const thinkingLevel = opts?.thinkingLevel ?? getThinkingLevel(settingsChat);
14611
15384
  const resolvedCwd = cwd ?? WORKSPACE_PATH;
14612
- 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");
14613
15386
  const effectiveAgentMode = optsAgentMode ?? getAgentMode(settingsChat);
14614
15387
  const sideQuestCtx = settingsSourceChatId ? { parentChatId: settingsSourceChatId, actualChatId: chatId } : void 0;
14615
15388
  const fullPrompt = await assembleBootstrapPrompt(userMessage, entityType, profile, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx, planningDirective, opts?.chatContext, adapter.type);
14616
15389
  if (adapter.streamDirect) {
14617
15390
  const resolvedModel2 = model2 ?? adapter.defaultModel;
14618
15391
  const abortController = new AbortController();
14619
- const cancelState2 = { cancelled: false, userMessage, abortController };
15392
+ const cancelState2 = { cancelled: false, userMessage, abortController, backendId: adapter.id, model: resolvedModel2 };
14620
15393
  activeChats.set(chatId, cancelState2);
14621
15394
  try {
14622
15395
  let messageHistory;
@@ -14676,9 +15449,16 @@ async function askAgentImpl(chatId, userMessage, opts) {
14676
15449
  };
14677
15450
  } finally {
14678
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
+ }
14679
15458
  }
14680
15459
  }
14681
15460
  const existingSessionId = settingsSourceChatId ? null : getSessionId(settingsChat);
15461
+ log(`[agent:session] chatId=${chatId} settingsChat=${settingsChat} existingSessionId=${existingSessionId ?? "(none)"} backend=${adapter.id}`);
14682
15462
  const allowedTools = getEnabledTools(settingsChat);
14683
15463
  const mcpConfigPath = profile !== "minimal" && effectiveAgentMode !== "native" && MCP_CONFIG_FLAG[adapter.id] ? getMcpConfigPath(chatId) : null;
14684
15464
  const baseConfig = adapter.buildSpawnConfig({
@@ -14917,7 +15697,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
14917
15697
  return { text: "Stopped.", usage: { input: result.input, output: result.output, cacheRead: result.cacheRead, contextSize: result.contextSize } };
14918
15698
  }
14919
15699
  if (result.sessionId && !isSyntheticChatId(chatId)) {
15700
+ log(`[agent:session] Persisting session for chatId=${chatId}: ${result.sessionId}`);
14920
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`);
14921
15704
  }
14922
15705
  if (adapter.id === BACKEND.CLAUDE && result.resultText && result.sessionId && /InputValidationError/i.test(result.resultText) && /missing|required parameter/i.test(result.resultText)) {
14923
15706
  const toolMatch = result.resultText.match(/(?:parameter\s+'|required parameter\s+['"]?)([\w-]+)/i);
@@ -14966,9 +15749,12 @@ async function askAgentImpl(chatId, userMessage, opts) {
14966
15749
  if (result.resultText && !isSyntheticChatId(chatId)) {
14967
15750
  appendToLog(chatId, userMessage, result.resultText, adapter.id, model2 ?? null, result.sessionId ?? null);
14968
15751
  const pairCount = profile !== "chat" ? getMessagePairCount(chatId) : 0;
14969
- if (pairCount >= 30) {
15752
+ if (pairCount >= 30 && !compactionInFlight.has(chatId)) {
15753
+ compactionInFlight.add(chatId);
14970
15754
  log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
14971
- runCompaction(chatId, "30-pair-threshold", opts?.onCompaction);
15755
+ runCompaction(chatId, "30-pair-threshold", opts?.onCompaction).finally(() => {
15756
+ compactionInFlight.delete(chatId);
15757
+ });
14972
15758
  }
14973
15759
  }
14974
15760
  return {
@@ -15513,52 +16299,6 @@ var init_pagination = __esm({
15513
16299
  }
15514
16300
  });
15515
16301
 
15516
- // src/format-time.ts
15517
- function formatLocalDate(utcDatetime) {
15518
- const d = parseUtcDatetime(utcDatetime);
15519
- if (!d) return utcDatetime.split("T")[0] ?? utcDatetime.split(" ")[0];
15520
- const year = d.getFullYear();
15521
- const month = String(d.getMonth() + 1).padStart(2, "0");
15522
- const day = String(d.getDate()).padStart(2, "0");
15523
- return `${year}-${month}-${day}`;
15524
- }
15525
- function formatLocalDateTime(utcDatetime) {
15526
- const d = parseUtcDatetime(utcDatetime);
15527
- if (!d) return utcDatetime;
15528
- const year = d.getFullYear();
15529
- const month = String(d.getMonth() + 1).padStart(2, "0");
15530
- const day = String(d.getDate()).padStart(2, "0");
15531
- const hours = String(d.getHours()).padStart(2, "0");
15532
- const minutes = String(d.getMinutes()).padStart(2, "0");
15533
- const tzAbbr = getTimezoneAbbr(d);
15534
- return `${year}-${month}-${day} ${hours}:${minutes} ${tzAbbr}`;
15535
- }
15536
- function parseUtcDatetime(utcDatetime) {
15537
- if (!utcDatetime) return null;
15538
- const normalized = utcDatetime.includes("T") ? utcDatetime : utcDatetime.replace(" ", "T");
15539
- const withZ = normalized.endsWith("Z") ? normalized : normalized + "Z";
15540
- const d = new Date(withZ);
15541
- return isNaN(d.getTime()) ? null : d;
15542
- }
15543
- function getTimezoneAbbr(date) {
15544
- try {
15545
- const parts = new Intl.DateTimeFormat("en-US", {
15546
- timeZone: systemTimezone,
15547
- timeZoneName: "short"
15548
- }).formatToParts(date);
15549
- return parts.find((p) => p.type === "timeZoneName")?.value ?? "";
15550
- } catch {
15551
- return "";
15552
- }
15553
- }
15554
- var systemTimezone;
15555
- var init_format_time = __esm({
15556
- "src/format-time.ts"() {
15557
- "use strict";
15558
- systemTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
15559
- }
15560
- });
15561
-
15562
16302
  // src/scheduler/humanize.ts
15563
16303
  function formatTime(hour, minute) {
15564
16304
  const h = hour % 12 || 12;
@@ -16851,11 +17591,11 @@ var init_health2 = __esm({
16851
17591
  });
16852
17592
 
16853
17593
  // src/health/checks.ts
16854
- 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";
16855
17595
  import { execFileSync as execFileSync2, execSync as execSync2 } from "child_process";
16856
17596
  function getRecentErrors() {
16857
17597
  if (!existsSync16(ERROR_LOG_PATH)) return null;
16858
- const logContent = readFileSync11(ERROR_LOG_PATH, "utf-8");
17598
+ const logContent = readFileSync12(ERROR_LOG_PATH, "utf-8");
16859
17599
  const allLines = logContent.split("\n").filter(Boolean).slice(-500);
16860
17600
  const last24h = Date.now() - 864e5;
16861
17601
  const lines = allLines.filter((line) => {
@@ -17084,7 +17824,7 @@ __export(heartbeat_exports, {
17084
17824
  stopHeartbeatForChat: () => stopHeartbeatForChat,
17085
17825
  updateHeartbeatConfig: () => updateHeartbeatConfig
17086
17826
  });
17087
- import { readFileSync as readFileSync12, existsSync as existsSync17 } from "fs";
17827
+ import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
17088
17828
  import { join as join17 } from "path";
17089
17829
  function findHeartbeatJob() {
17090
17830
  try {
@@ -17266,7 +18006,7 @@ ${watchLines.join("\n")}`);
17266
18006
  }
17267
18007
  if (existsSync17(HEARTBEAT_MD_PATH)) {
17268
18008
  try {
17269
- const custom = readFileSync12(HEARTBEAT_MD_PATH, "utf-8").trim();
18009
+ const custom = readFileSync13(HEARTBEAT_MD_PATH, "utf-8").trim();
17270
18010
  if (custom) {
17271
18011
  sections.push(`[Custom checks from HEARTBEAT.md]
17272
18012
  ${custom}`);
@@ -17515,6 +18255,7 @@ __export(ui_exports, {
17515
18255
  ROTATION_MODE_LABELS: () => ROTATION_MODE_LABELS,
17516
18256
  buildAccountSlotKeyboard: () => buildAccountSlotKeyboard,
17517
18257
  doBackendSwitch: () => doBackendSwitch,
18258
+ executeBackendDisable: () => executeBackendDisable,
17518
18259
  getJobScheduleText: () => getJobScheduleText,
17519
18260
  getJobStatusEmoji: () => getJobStatusEmoji,
17520
18261
  getJobStatusLabel: () => getJobStatusLabel,
@@ -17522,6 +18263,7 @@ __export(ui_exports, {
17522
18263
  sendApiToolsKeyboard: () => sendApiToolsKeyboard,
17523
18264
  sendBackendAccountPicker: () => sendBackendAccountPicker,
17524
18265
  sendBackendConfigPanel: () => sendBackendConfigPanel,
18266
+ sendBackendDisableFlow: () => sendBackendDisableFlow,
17525
18267
  sendBackendModelPicker: () => sendBackendModelPicker,
17526
18268
  sendBackendPicker: () => sendBackendPicker,
17527
18269
  sendBackendSwitchConfirmation: () => sendBackendSwitchConfirmation,
@@ -18265,9 +19007,24 @@ ${unapproved.length} CC-Claw skill${unapproved.length !== 1 ? "s" : ""} need rev
18265
19007
  const safePage = Math.max(1, Math.min(page, totalPages));
18266
19008
  const start = (safePage - 1) * SKILLS_PER_PAGE;
18267
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
+ }
18268
19020
  const typeIcon = (s) => s.type === "specialist" ? "\u{1F3AF}" : "\u{1F527}";
18269
19021
  const buttons = pageSkills.map((s) => {
18270
- 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
+ ];
18271
19028
  });
18272
19029
  if (totalPages > 1) {
18273
19030
  const navRow = [];
@@ -18999,6 +19756,17 @@ async function sendBackendPicker(chatId, channel, messageId) {
18999
19756
  }
19000
19757
  async function sendBackendConfigPanel(chatId, backendId, channel, messageId, switched = false) {
19001
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
+ }
19002
19770
  const summary = backendConfigSummary(chatId, backendId, switched);
19003
19771
  const buttons = [
19004
19772
  [
@@ -19006,10 +19774,80 @@ async function sendBackendConfigPanel(chatId, backendId, channel, messageId, swi
19006
19774
  { label: "Thinking", data: `bconf:thinking:${backendId}` },
19007
19775
  { label: "Account", data: `bconf:account:${backendId}` }
19008
19776
  ],
19777
+ [{ label: "Disable", data: `bconf:disable:${backendId}`, style: "danger" }],
19009
19778
  [{ label: "\u2190 Back to Backends", data: "bconf:back" }]
19010
19779
  ];
19011
19780
  await sendOrEditKeyboard(chatId, channel, messageId, summary, buttons);
19012
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
+ }
19013
19851
  async function sendBackendModelPicker(chatId, backendId, channel, messageId) {
19014
19852
  if (typeof channel.sendKeyboard !== "function") return;
19015
19853
  const adapter = getAdapter(backendId);
@@ -19083,7 +19921,15 @@ async function sendBackendThinkingPicker(chatId, backendId, channel, messageId)
19083
19921
  return;
19084
19922
  }
19085
19923
  const current = getThinkingLevel(chatId) ?? "auto";
19086
- 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 = [
19087
19933
  ["auto", "Auto"],
19088
19934
  ["off", "Off"],
19089
19935
  ["low", "Low"],
@@ -19091,6 +19937,7 @@ async function sendBackendThinkingPicker(chatId, backendId, channel, messageId)
19091
19937
  ["high", "High"],
19092
19938
  ["extra_high", "Extra High"]
19093
19939
  ];
19940
+ const levels = modelInfo?.thinkingLevels ? modelInfo.thinkingLevels.map((l) => [l, LEVEL_LABELS[l] ?? capitalize(l.replace("_", " "))]) : defaultLevels;
19094
19941
  const buttons = levels.map(([val, label2]) => [{
19095
19942
  label: `${val === current ? "\u2713 " : ""}${label2}`,
19096
19943
  data: `bconf:setthinking:${backendId}:${val}`,
@@ -19151,6 +19998,16 @@ async function doBackendSwitch(chatId, backendId, channel, opts) {
19151
19998
  const bridge = buildContextBridge(chatId, 15, interrupted);
19152
19999
  if (bridge) setPendingContextBridge(chatId, bridge);
19153
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
+ }
19154
20011
  clearSession(chatId);
19155
20012
  clearModel(chatId);
19156
20013
  clearThinkingLevel(chatId);
@@ -20641,7 +21498,7 @@ __export(analyze_exports, {
20641
21498
  });
20642
21499
  import { spawn as spawn5 } from "child_process";
20643
21500
  import { createInterface as createInterface4 } from "readline";
20644
- 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";
20645
21502
  import { join as join18 } from "path";
20646
21503
  import { homedir as homedir6 } from "os";
20647
21504
  function applySignalDecay(confidence, createdAt) {
@@ -20663,7 +21520,7 @@ function discoverReflectionTargets() {
20663
21520
  if (!existsSync18(skillFile)) continue;
20664
21521
  let desc = "skill";
20665
21522
  try {
20666
- const content = readFileSync13(skillFile, "utf-8");
21523
+ const content = readFileSync14(skillFile, "utf-8");
20667
21524
  const descMatch = content.match(/description:\s*["']?([^"'\n]+)/);
20668
21525
  if (descMatch) desc = descMatch[1].trim().slice(0, 80);
20669
21526
  } catch {
@@ -20738,6 +21595,21 @@ ${categoryList}`);
20738
21595
  sections.push(skill.content);
20739
21596
  }
20740
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
+ }
20741
21613
  sections.push("[Previously Applied Insights]");
20742
21614
  if (appliedInsights.length === 0) {
20743
21615
  sections.push("(none)");
@@ -20872,7 +21744,7 @@ function resolveReflectionAdapter(chatId) {
20872
21744
  }
20873
21745
  function readIdentityFile(filename) {
20874
21746
  try {
20875
- return readFileSync13(join18(IDENTITY_PATH, filename), "utf-8");
21747
+ return readFileSync14(join18(IDENTITY_PATH, filename), "utf-8");
20876
21748
  } catch {
20877
21749
  return "";
20878
21750
  }
@@ -21031,7 +21903,7 @@ async function runAnalysisImpl(chatId, opts) {
21031
21903
  try {
21032
21904
  const fullPath = join18(ccClawHome, target.path);
21033
21905
  if (existsSync18(fullPath)) {
21034
- const content = readFileSync13(fullPath, "utf-8");
21906
+ const content = readFileSync14(fullPath, "utf-8");
21035
21907
  if (totalSkillChars + content.length > SKILL_CONTENT_CAP) break;
21036
21908
  skillContents.push({ path: target.path, content });
21037
21909
  totalSkillChars += content.length;
@@ -21039,6 +21911,7 @@ async function runAnalysisImpl(chatId, opts) {
21039
21911
  } catch {
21040
21912
  }
21041
21913
  }
21914
+ const skillCorrections = getSkillCorrections(db3, 30, 3);
21042
21915
  const prompt = buildAnalysisPrompt({
21043
21916
  signals: signals.map((s) => {
21044
21917
  const decayedConfidence = applySignalDecay(s.confidence, s.created_at);
@@ -21061,7 +21934,8 @@ async function runAnalysisImpl(chatId, opts) {
21061
21934
  })),
21062
21935
  codebaseEnabled,
21063
21936
  availableTargets,
21064
- skillContents
21937
+ skillContents,
21938
+ skillCorrections
21065
21939
  });
21066
21940
  const resolved = resolveReflectionAdapter(chatId);
21067
21941
  if (!resolved) {
@@ -21411,7 +22285,7 @@ __export(apply_exports, {
21411
22285
  isTargetAllowed: () => isTargetAllowed,
21412
22286
  rollbackInsight: () => rollbackInsight
21413
22287
  });
21414
- 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";
21415
22289
  import { join as join19, dirname as dirname4 } from "path";
21416
22290
  function isTargetAllowed(relativePath) {
21417
22291
  if (relativePath.includes("..")) return false;
@@ -21499,7 +22373,7 @@ async function applyInsight(insightId) {
21499
22373
  const absolutePath = join19(CC_CLAW_HOME, insight.targetFile);
21500
22374
  if (insight.proposedAction === "append" && insight.targetFile === "identity/SOUL.md") {
21501
22375
  if (existsSync19(absolutePath)) {
21502
- const currentContent = readFileSync14(absolutePath, "utf-8");
22376
+ const currentContent = readFileSync15(absolutePath, "utf-8");
21503
22377
  const lineCount = currentContent.split("\n").length;
21504
22378
  if (lineCount >= SOUL_LINE_CAP) {
21505
22379
  return {
@@ -21521,7 +22395,7 @@ async function applyInsight(insightId) {
21521
22395
  }
21522
22396
  let original = "";
21523
22397
  if (existsSync19(absolutePath)) {
21524
- original = readFileSync14(absolutePath, "utf-8");
22398
+ original = readFileSync15(absolutePath, "utf-8");
21525
22399
  } else if (insight.proposedAction !== "create") {
21526
22400
  return { success: false, message: `Target file "${insight.targetFile}" does not exist` };
21527
22401
  }
@@ -21662,7 +22536,7 @@ function computeLineDrift(baseline, absolutePath) {
21662
22536
  let current = "";
21663
22537
  try {
21664
22538
  if (existsSync19(absolutePath)) {
21665
- current = readFileSync14(absolutePath, "utf-8");
22539
+ current = readFileSync15(absolutePath, "utf-8");
21666
22540
  }
21667
22541
  } catch {
21668
22542
  return 1;
@@ -22394,6 +23268,14 @@ var init_api_mcp = __esm({
22394
23268
 
22395
23269
  // src/backends/api-common.ts
22396
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
+ }
22397
23279
  function toModelMessage(msg) {
22398
23280
  switch (msg.role) {
22399
23281
  case "system":
@@ -22590,6 +23472,7 @@ var init_ollama2 = __esm({
22590
23472
  "src/backends/ollama.ts"() {
22591
23473
  "use strict";
22592
23474
  init_api_common();
23475
+ init_api_common();
22593
23476
  init_ollama();
22594
23477
  init_prompt();
22595
23478
  OLLAMA_HTTP_SENTINEL = "__ollama_http__";
@@ -22677,29 +23560,31 @@ var init_ollama2 = __esm({
22677
23560
  */
22678
23561
  async streamDirect(prompt, model2, opts) {
22679
23562
  const cleanPrompt = stripForLocalModel(prompt);
22680
- let disableThinking = false;
22681
23563
  let modelContextWindow;
23564
+ const ollamaProviderOpts = {};
22682
23565
  try {
22683
23566
  const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
22684
23567
  const modelRecord = OllamaStore.getModelByName(model2);
22685
- if (modelRecord?.forceThinkOff) {
22686
- disableThinking = true;
22687
- } else if (opts?.thinkingLevel === "off") {
22688
- 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;
22689
23575
  }
22690
23576
  if (modelRecord?.contextWindow && modelRecord.contextWindow > 4096) {
22691
23577
  modelContextWindow = modelRecord.contextWindow;
22692
23578
  }
22693
23579
  } catch {
22694
23580
  }
22695
- const ollamaProviderOpts = {};
22696
- if (disableThinking) ollamaProviderOpts.think = false;
22697
23581
  if (modelContextWindow) ollamaProviderOpts.num_ctx = modelContextWindow;
22698
23582
  const apiOpts = {
22699
23583
  timeoutMs: opts?.timeoutMs,
22700
23584
  onStream: opts?.onStream,
22701
23585
  signal: opts?.signal,
22702
23586
  messageHistory: opts?.messageHistory,
23587
+ onToolAction: mapToolActionToApiFormat(opts?.onToolAction),
22703
23588
  permMode: opts?.permMode,
22704
23589
  thinkingLevel: opts?.thinkingLevel,
22705
23590
  onThinking: opts?.onThinking,
@@ -22728,10 +23613,11 @@ var init_ollama2 = __esm({
22728
23613
  const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
22729
23614
  const models = OllamaStore.getAvailableModels();
22730
23615
  for (const m of models) {
22731
- const isThinkingCapable = m.capability === "thinking" && !m.forceThinkOff;
22732
23616
  this.availableModels[m.name] = {
22733
23617
  label: `${m.name}${m.parameterSize ? ` (${m.parameterSize})` : ""}`,
22734
- thinking: isThinkingCapable ? "adjustable" : "none"
23618
+ thinking: "adjustable",
23619
+ // all Ollama models support think: true/false toggle
23620
+ thinkingLevels: ["auto", "off", "high"]
22735
23621
  };
22736
23622
  this.pricing[m.name] = { in: 0, out: 0, cache: 0 };
22737
23623
  this.contextWindow[m.name] = m.contextWindow ?? 4096;
@@ -22748,6 +23634,7 @@ var init_openrouter = __esm({
22748
23634
  "src/backends/openrouter.ts"() {
22749
23635
  "use strict";
22750
23636
  init_api_common();
23637
+ init_api_common();
22751
23638
  OPENROUTER_HTTP_SENTINEL = "__openrouter_api__";
22752
23639
  DEFAULT_FREE_MODEL = "qwen/qwen3.6-plus:free";
22753
23640
  OpenRouterAdapter = class extends ApiBackendBase {
@@ -22837,17 +23724,12 @@ var init_openrouter = __esm({
22837
23724
  * including tool actions, permission mode, thinking tokens, and conversation history.
22838
23725
  */
22839
23726
  async streamDirect(prompt, model2, opts) {
22840
- const onToolAction = opts?.onToolAction ? (event) => {
22841
- const input = event.input ?? {};
22842
- const result2 = event.type === "tool_end" ? typeof event.output === "string" ? event.output : JSON.stringify(event.output ?? "") : void 0;
22843
- opts.onToolAction(event.toolName, input, result2);
22844
- } : void 0;
22845
23727
  const apiOpts = {
22846
23728
  timeoutMs: opts?.timeoutMs,
22847
23729
  onStream: opts?.onStream,
22848
23730
  signal: opts?.signal,
22849
23731
  messageHistory: opts?.messageHistory,
22850
- onToolAction,
23732
+ onToolAction: mapToolActionToApiFormat(opts?.onToolAction),
22851
23733
  permMode: opts?.permMode,
22852
23734
  thinkingLevel: opts?.thinkingLevel,
22853
23735
  onThinking: opts?.onThinking
@@ -23023,7 +23905,10 @@ function getAvailableBackendIds() {
23023
23905
  return [...availableSet];
23024
23906
  }
23025
23907
  function getAvailableChatBackendIds() {
23026
- 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
+ );
23027
23912
  }
23028
23913
  var openRouterAdapter, adapters, CHAT_BACKEND_IDS, availableSet;
23029
23914
  var init_backends = __esm({
@@ -23036,6 +23921,7 @@ var init_backends = __esm({
23036
23921
  init_ollama2();
23037
23922
  init_openrouter();
23038
23923
  init_store5();
23924
+ init_schema();
23039
23925
  init_log();
23040
23926
  init_types();
23041
23927
  openRouterAdapter = new OpenRouterAdapter();
@@ -23325,7 +24211,7 @@ var init_retry = __esm({
23325
24211
  });
23326
24212
 
23327
24213
  // src/bootstrap/profile.ts
23328
- 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";
23329
24215
  import { join as join21 } from "path";
23330
24216
  function hasActiveProfile(chatId) {
23331
24217
  return activeProfiles.has(chatId);
@@ -23456,7 +24342,7 @@ function extractUserUpdates(text) {
23456
24342
  }
23457
24343
  function appendToUserProfile(key, value) {
23458
24344
  if (!existsSync22(USER_PATH2)) return;
23459
- const content = readFileSync15(USER_PATH2, "utf-8");
24345
+ const content = readFileSync16(USER_PATH2, "utf-8");
23460
24346
  const line = `- **${key}**: ${value}`;
23461
24347
  if (content.includes(line)) return;
23462
24348
  const updated = content.trimEnd() + `
@@ -26759,12 +27645,12 @@ async function handleEvolveCallback(chatId, data, channel, messageId) {
26759
27645
  );
26760
27646
  break;
26761
27647
  }
26762
- const { readFileSync: readFileSync35, existsSync: existsSync62 } = await import("fs");
27648
+ const { readFileSync: readFileSync36, existsSync: existsSync62 } = await import("fs");
26763
27649
  const { join: join42 } = await import("path");
26764
27650
  const targetPath = join42(homedir7(), ".cc-claw", insight.targetFile);
26765
27651
  let previewText;
26766
27652
  if (existsSync62(targetPath)) {
26767
- const current = readFileSync35(targetPath, "utf-8");
27653
+ const current = readFileSync36(targetPath, "utf-8");
26768
27654
  const diffLines = insight.proposedDiff.split("\n");
26769
27655
  const additions = diffLines.filter((l) => l.startsWith("+")).map((l) => ` + ${l.slice(1).trim()}`);
26770
27656
  const removals = diffLines.filter((l) => l.startsWith("-")).map((l) => ` - ${l.slice(1).trim()}`);
@@ -26836,13 +27722,13 @@ async function handleEvolveCallback(chatId, data, channel, messageId) {
26836
27722
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
26837
27723
  const current = getReflectionStatus2(getDb(), chatId);
26838
27724
  if (current === "frozen") {
26839
- const { readFileSync: readFileSync35, existsSync: existsSync62 } = await import("fs");
27725
+ const { readFileSync: readFileSync36, existsSync: existsSync62 } = await import("fs");
26840
27726
  const { join: join42 } = await import("path");
26841
27727
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
26842
27728
  const soulPath = join42(CC_CLAW_HOME3, "identity/SOUL.md");
26843
27729
  const userPath = join42(CC_CLAW_HOME3, "identity/USER.md");
26844
- const soul = existsSync62(soulPath) ? readFileSync35(soulPath, "utf-8") : "";
26845
- 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") : "";
26846
27732
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
26847
27733
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
26848
27734
  await sendEvolveDashboard(chatId, reflChatId, channel, messageId);
@@ -26981,11 +27867,11 @@ var init_evolve2 = __esm({
26981
27867
  });
26982
27868
 
26983
27869
  // src/optimizer/identity-audit.ts
26984
- 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";
26985
27871
  import { join as join26 } from "path";
26986
27872
  function readIdentityFile2(filename) {
26987
27873
  try {
26988
- return readFileSync16(join26(IDENTITY_PATH, filename), "utf-8");
27874
+ return readFileSync17(join26(IDENTITY_PATH, filename), "utf-8");
26989
27875
  } catch {
26990
27876
  return "";
26991
27877
  }
@@ -27138,7 +28024,7 @@ var init_identity_audit = __esm({
27138
28024
  });
27139
28025
 
27140
28026
  // src/optimizer/skill-audit.ts
27141
- import { readFileSync as readFileSync17, existsSync as existsSync27 } from "fs";
28027
+ import { readFileSync as readFileSync18, existsSync as existsSync27 } from "fs";
27142
28028
  import { join as join27, basename as basename3 } from "path";
27143
28029
  function parseFrontmatter3(content) {
27144
28030
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
@@ -27184,7 +28070,7 @@ function detectDependentSkills(content) {
27184
28070
  return Array.from(deps);
27185
28071
  }
27186
28072
  function computeSkillStats(skillPath) {
27187
- const content = readFileSync17(skillPath, "utf-8");
28073
+ const content = readFileSync18(skillPath, "utf-8");
27188
28074
  const lines = content.split("\n");
27189
28075
  return {
27190
28076
  skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join27(skillPath, "..")) : basename3(skillPath, ".md"),
@@ -27213,7 +28099,7 @@ function loadDependentSkillContents(depNames, ccClawSkillsDir) {
27213
28099
  for (const candidate of candidates) {
27214
28100
  if (existsSync27(candidate)) {
27215
28101
  try {
27216
- const content = readFileSync17(candidate, "utf-8");
28102
+ const content = readFileSync18(candidate, "utf-8");
27217
28103
  results.push({
27218
28104
  name,
27219
28105
  content: content.length > 3e3 ? content.slice(0, 3e3) + "\n[...truncated]" : content
@@ -27357,7 +28243,7 @@ __export(analyze_exports2, {
27357
28243
  });
27358
28244
  import { spawn as spawn7 } from "child_process";
27359
28245
  import { createInterface as createInterface7 } from "readline";
27360
- 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";
27361
28247
  import { join as join28 } from "path";
27362
28248
  import { homedir as homedir8 } from "os";
27363
28249
  function parseOptimizeOutput(raw, validAreas) {
@@ -27488,7 +28374,7 @@ function getModelDisplayInfo(chatId) {
27488
28374
  }
27489
28375
  function readIdentityFile3(filename) {
27490
28376
  try {
27491
- return readFileSync18(join28(IDENTITY_PATH, filename), "utf-8");
28377
+ return readFileSync19(join28(IDENTITY_PATH, filename), "utf-8");
27492
28378
  } catch {
27493
28379
  return "";
27494
28380
  }
@@ -27501,7 +28387,7 @@ function loadContextFiles() {
27501
28387
  for (const entry of readdirSync12(contextDir)) {
27502
28388
  if (!entry.endsWith(".md")) continue;
27503
28389
  try {
27504
- const content = readFileSync18(join28(contextDir, entry), "utf-8");
28390
+ const content = readFileSync19(join28(contextDir, entry), "utf-8");
27505
28391
  results.push({ name: entry, content });
27506
28392
  } catch {
27507
28393
  }
@@ -27553,7 +28439,7 @@ async function runSkillAudit(chatId, skillPath) {
27553
28439
  log(`[optimizer] Running skill audit on ${stats.skillName} with ${adapter.id}:${model2}`);
27554
28440
  const soulMd = readIdentityFile3("SOUL.md");
27555
28441
  const ccClawSkillsDir = join28(homedir8(), ".cc-claw", "workspace", "skills");
27556
- const skillContent = readFileSync18(skillPath, "utf-8");
28442
+ const skillContent = readFileSync19(skillPath, "utf-8");
27557
28443
  const prompt = buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir);
27558
28444
  const raw = await spawnAnalysis2(adapter, model2, prompt);
27559
28445
  const findings = parseOptimizeOutput(raw, VALID_SKILL_AREAS);
@@ -27576,7 +28462,7 @@ function listCcClawSkills() {
27576
28462
  if (!existsSync28(skillFile)) continue;
27577
28463
  let description = "skill";
27578
28464
  try {
27579
- const content = readFileSync18(skillFile, "utf-8");
28465
+ const content = readFileSync19(skillFile, "utf-8");
27580
28466
  const descMatch = content.match(/description:\s*>?\s*\n?\s*(.+)/);
27581
28467
  if (descMatch) description = descMatch[1].trim().slice(0, 60);
27582
28468
  } catch {
@@ -27903,7 +28789,7 @@ __export(optimize_exports2, {
27903
28789
  handleOptimizeCallback: () => handleOptimizeCallback,
27904
28790
  handleOptimizeCommand: () => handleOptimizeCommand
27905
28791
  });
27906
- 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";
27907
28793
  import { join as join29, dirname as dirname5 } from "path";
27908
28794
  import { homedir as homedir9 } from "os";
27909
28795
  async function handleOptimizeCommand(chatId, channel, _args, messageId) {
@@ -28133,7 +29019,7 @@ async function applyFinding(chatId, channel, index, messageId) {
28133
29019
  await showFinding(chatId, channel, index + 1, effectiveMsgId);
28134
29020
  return;
28135
29021
  }
28136
- const original = readFileSync19(targetPath, "utf-8");
29022
+ const original = readFileSync20(targetPath, "utf-8");
28137
29023
  const backupPath = targetPath + `.bak.${Date.now()}`;
28138
29024
  writeFileSync9(backupPath, original, "utf-8");
28139
29025
  pruneBackups2(targetPath);
@@ -29688,14 +30574,19 @@ async function handleStatusCommand(chatId, commandArgs, msg, channel) {
29688
30574
  const iDeep = iStats.agentic;
29689
30575
  const intentLine = iTotal > 0 ? `\u26A1 ${iTotal} messages: ${iQuick} quick, ${iDeep} deep` : `\u26A1 No messages classified yet`;
29690
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`;
29691
30580
  const lines = [
29692
30581
  `\u{1F43E} CC-Claw v${VERSION}`,
29693
30582
  `\u23F1 Uptime: ${uptimeStr}`,
30583
+ isChatBusy(chatId) ? `\u{1F534} Agent: Busy` : `\u{1F7E2} Agent: Idle`,
29694
30584
  ``,
29695
30585
  buildSectionHeader("Engine"),
29696
30586
  `\u{1F9E0} ${adapter?.displayName ?? backendId ?? "not set"} \xB7 ${formatModelShort(model2)}${slotInfo}`,
29697
30587
  `\u{1F4AD} Think: ${thinking2} \xB7 Mode: ${mode}`,
29698
30588
  `\u{1F916} Agents: ${getAgentMode(chatId)}`,
30589
+ pinnedLine,
29699
30590
  `\u{1F507} Voice: ${voice2 ? "on" : "off"} \xB7 Sig: ${modelSig}`,
29700
30591
  `\u{1F4DD} Summarizer: ${summarizerStatusLine(chatId, adapter)}`,
29701
30592
  ``,
@@ -29741,11 +30632,16 @@ async function handleBackendCommand2(chatId, commandArgs, msg, channel) {
29741
30632
  async function handleBackendShortcutCommand(chatId, commandArgs, msg, channel) {
29742
30633
  const command = msg.command;
29743
30634
  const backendId = command;
29744
- if (getAllBackendIds().includes(backendId)) {
29745
- await sendBackendSwitchConfirmation(chatId, backendId, channel);
29746
- } else {
30635
+ if (!getAllBackendIds().includes(backendId)) {
29747
30636
  await channel.sendText(chatId, `Backend "${command}" is not available.`, { parseMode: "plain" });
30637
+ return;
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;
29748
30643
  }
30644
+ await sendBackendSwitchConfirmation(chatId, backendId, channel);
29749
30645
  }
29750
30646
  async function handleModelCommand(chatId, commandArgs, msg, channel) {
29751
30647
  await sendModelKeyboard(chatId, channel);
@@ -30895,7 +31791,7 @@ __export(import_exports, {
30895
31791
  parseClaudeConfig: () => parseClaudeConfig,
30896
31792
  parseCursorConfig: () => parseCursorConfig
30897
31793
  });
30898
- import { readFileSync as readFileSync20 } from "fs";
31794
+ import { readFileSync as readFileSync21 } from "fs";
30899
31795
  import { join as join31 } from "path";
30900
31796
  import { homedir as homedir10 } from "os";
30901
31797
  function parseClaudeConfig(config2) {
@@ -30912,7 +31808,7 @@ async function discoverFromSource(source) {
30912
31808
  case "claude": {
30913
31809
  const path = join31(homedir10(), ".claude", "settings.json");
30914
31810
  try {
30915
- return parseClaudeConfig(JSON.parse(readFileSync20(path, "utf-8")));
31811
+ return parseClaudeConfig(JSON.parse(readFileSync21(path, "utf-8")));
30916
31812
  } catch {
30917
31813
  warn(`[mcp-import] Could not read ${path}`);
30918
31814
  return [];
@@ -30921,7 +31817,7 @@ async function discoverFromSource(source) {
30921
31817
  case "cursor": {
30922
31818
  const path = join31(homedir10(), ".cursor", "mcp.json");
30923
31819
  try {
30924
- return parseCursorConfig(JSON.parse(readFileSync20(path, "utf-8")));
31820
+ return parseCursorConfig(JSON.parse(readFileSync21(path, "utf-8")));
30925
31821
  } catch {
30926
31822
  warn(`[mcp-import] Could not read ${path}`);
30927
31823
  return [];
@@ -31585,6 +32481,10 @@ async function handleCallback(chatId, data, channel, messageId) {
31585
32481
  if (data.startsWith("backend:")) {
31586
32482
  const chosen = data.slice(8);
31587
32483
  if (!getAllBackendIds().includes(chosen)) return;
32484
+ if (getDisabledBackends().includes(chosen)) {
32485
+ await sendBackendConfigPanel(chatId, chosen, channel, messageId, false);
32486
+ return;
32487
+ }
31588
32488
  const previous = getBackend(chatId);
31589
32489
  if (chosen === previous) {
31590
32490
  await sendBackendConfigPanel(chatId, chosen, channel, messageId, false);
@@ -31638,6 +32538,32 @@ async function handleCallback(chatId, data, channel, messageId) {
31638
32538
  }
31639
32539
  }
31640
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
+ });
31641
32567
  }
31642
32568
  } else if (data.startsWith("backend_confirm_clear:")) {
31643
32569
  const target = data.slice("backend_confirm_clear:".length);
@@ -33478,10 +34404,10 @@ ${formatValidationReport2(validation)}` : "\n\n\u2705 Quality checks passed.";
33478
34404
  return;
33479
34405
  }
33480
34406
  try {
33481
- const { readFileSync: readFileSync35, writeFileSync: writeFileSync16 } = await import("fs");
34407
+ const { readFileSync: readFileSync36, writeFileSync: writeFileSync16 } = await import("fs");
33482
34408
  const { updateFrontmatter: updateFrontmatter2, ensureThreeTierFrontmatter: ensureThreeTierFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
33483
34409
  const { invalidateSkillCache: invalidateSkillCache2 } = await Promise.resolve().then(() => (init_discover(), discover_exports));
33484
- const raw = readFileSync35(skill.filePath, "utf-8");
34410
+ const raw = readFileSync36(skill.filePath, "utf-8");
33485
34411
  let updated = ensureThreeTierFrontmatter2(raw, { name: skillName, source: "cc-claw" });
33486
34412
  updated = updateFrontmatter2(updated, { status: "approved" });
33487
34413
  writeFileSync16(skill.filePath, updated, "utf-8");
@@ -33531,14 +34457,14 @@ ${stillUnapproved.length} more skill${stillUnapproved.length !== 1 ? "s" : ""} t
33531
34457
  return;
33532
34458
  }
33533
34459
  try {
33534
- const { readFileSync: readFileSync35, writeFileSync: writeFileSync16 } = await import("fs");
34460
+ const { readFileSync: readFileSync36, writeFileSync: writeFileSync16 } = await import("fs");
33535
34461
  const { updateFrontmatter: updateFrontmatter2, ensureThreeTierFrontmatter: ensureThreeTierFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
33536
34462
  const { invalidateSkillCache: invalidateSkillCache2 } = await Promise.resolve().then(() => (init_discover(), discover_exports));
33537
34463
  let approvedCount = 0;
33538
34464
  const issues = [];
33539
34465
  for (const skill of unapproved) {
33540
34466
  try {
33541
- const raw = readFileSync35(skill.filePath, "utf-8");
34467
+ const raw = readFileSync36(skill.filePath, "utf-8");
33542
34468
  let updated = ensureThreeTierFrontmatter2(raw, { name: skill.name, source: "cc-claw" });
33543
34469
  updated = updateFrontmatter2(updated, { status: "approved" });
33544
34470
  writeFileSync16(skill.filePath, updated, "utf-8");
@@ -33572,6 +34498,29 @@ ${issues.join("\n")}`;
33572
34498
  );
33573
34499
  }
33574
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
+ }
33575
34524
  } else if (data.startsWith("skill:")) {
33576
34525
  const parts = data.slice(6).split(":");
33577
34526
  let skillName;
@@ -33779,134 +34728,6 @@ var init_thread_wrapper = __esm({
33779
34728
  }
33780
34729
  });
33781
34730
 
33782
- // src/intent/complexity.ts
33783
- var complexity_exports = {};
33784
- __export(complexity_exports, {
33785
- classifyComplexity: () => classifyComplexity
33786
- });
33787
- function wordCount(text) {
33788
- return text.trim().split(/\s+/).filter(Boolean).length;
33789
- }
33790
- function classifyComplexity(text) {
33791
- const trimmed = text.trim();
33792
- const lower = trimmed.toLowerCase();
33793
- const words = wordCount(trimmed);
33794
- if (TRIVIAL_EXACT.has(lower)) {
33795
- log(`[complexity] "${lower}" -> trivial (exact match)`);
33796
- return "trivial";
33797
- }
33798
- if (trimmed.length <= 4 && /^[\p{Emoji}\s]+$/u.test(trimmed)) {
33799
- log(`[complexity] "${trimmed}" -> trivial (emoji-only)`);
33800
- return "trivial";
33801
- }
33802
- if (COMPLEX_SIGNALS.test(trimmed) && (words > 15 || trimmed.length > 200)) {
33803
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> complex (signal words + length)`);
33804
- return "complex";
33805
- }
33806
- if (MUTATION_VERBS.test(trimmed)) {
33807
- const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
33808
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (mutation verb)`);
33809
- return tier;
33810
- }
33811
- for (const pattern of CODE_PATTERNS) {
33812
- if (pattern.test(trimmed)) {
33813
- const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
33814
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (code pattern)`);
33815
- return tier;
33816
- }
33817
- }
33818
- if (trimmed.length > 200 || words > 25) {
33819
- const tier = words > 30 || trimmed.length > 300 ? "complex" : "moderate";
33820
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> ${tier} (long message)`);
33821
- return tier;
33822
- }
33823
- for (const pattern of QUESTION_PATTERNS) {
33824
- if (pattern.test(trimmed)) {
33825
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> simple (question)`);
33826
- return "simple";
33827
- }
33828
- }
33829
- log(`[complexity] "${trimmed.slice(0, 40)}..." -> moderate (default)`);
33830
- return "moderate";
33831
- }
33832
- var TRIVIAL_EXACT, COMPLEX_SIGNALS, MUTATION_VERBS, CODE_PATTERNS, QUESTION_PATTERNS;
33833
- var init_complexity = __esm({
33834
- "src/intent/complexity.ts"() {
33835
- "use strict";
33836
- init_log();
33837
- TRIVIAL_EXACT = /* @__PURE__ */ new Set([
33838
- "hey",
33839
- "hi",
33840
- "hello",
33841
- "yo",
33842
- "sup",
33843
- "howdy",
33844
- "hiya",
33845
- "thanks",
33846
- "thank you",
33847
- "thx",
33848
- "ty",
33849
- "thank u",
33850
- "ok",
33851
- "okay",
33852
- "k",
33853
- "kk",
33854
- "cool",
33855
- "nice",
33856
- "great",
33857
- "awesome",
33858
- "perfect",
33859
- "good morning",
33860
- "good night",
33861
- "good evening",
33862
- "gm",
33863
- "gn",
33864
- "bye",
33865
- "goodbye",
33866
- "later",
33867
- "see ya",
33868
- "cya",
33869
- "lol",
33870
- "lmao",
33871
- "haha",
33872
- "heh",
33873
- "np",
33874
- "no problem",
33875
- "no worries",
33876
- "nw",
33877
- "got it",
33878
- "understood",
33879
- "roger",
33880
- "copy",
33881
- "good",
33882
- "fine",
33883
- "alright",
33884
- "sure",
33885
- "yes",
33886
- "no",
33887
- "yep",
33888
- "nope",
33889
- "yeah",
33890
- "nah"
33891
- ]);
33892
- COMPLEX_SIGNALS = /\b(architect|design|research|plan|strategy|migration|microservice|distributed|end-to-end|trade-offs|pros\s+and\s+cons)\b/i;
33893
- MUTATION_VERBS = /\b(fix|create|build|deploy|refactor|implement|write|add|update|edit|test|debug)\b/i;
33894
- CODE_PATTERNS = [
33895
- /```/,
33896
- // code blocks
33897
- /\.[a-z]{1,5}\b/,
33898
- // file extensions (.ts, .py, .json)
33899
- /[/\\][\w.-]+/
33900
- // file paths (/src/foo, .\bar)
33901
- ];
33902
- QUESTION_PATTERNS = [
33903
- /^(?:what|which|where|when|why|how)\b/i,
33904
- /^(?:show|tell|list|explain)\b/i,
33905
- /^(?:is|are)\b/i
33906
- ];
33907
- }
33908
- });
33909
-
33910
34731
  // src/intent/auto-route.ts
33911
34732
  var auto_route_exports = {};
33912
34733
  __export(auto_route_exports, {
@@ -35257,6 +36078,12 @@ async function executeJob(job) {
35257
36078
  log(`[scheduler] Running job #${job.id}: ${job.description} (model=${resolvedModel})`);
35258
36079
  try {
35259
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
+ }
35260
36087
  const limitMsg = checkBackendLimits(backendId);
35261
36088
  if (limitMsg) {
35262
36089
  completeJobRun(runId, "skipped", { error: limitMsg, durationMs: Date.now() - t0 });
@@ -35687,7 +36514,7 @@ var init_wrap_backend = __esm({
35687
36514
  });
35688
36515
 
35689
36516
  // src/agents/runners/config-loader.ts
35690
- 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";
35691
36518
  import { join as join33 } from "path";
35692
36519
  import { execFileSync as execFileSync3 } from "child_process";
35693
36520
  function resolveExecutable2(config2) {
@@ -35829,7 +36656,7 @@ function configToRunner(config2) {
35829
36656
  }
35830
36657
  function loadRunnerConfig(filePath) {
35831
36658
  try {
35832
- const content = readFileSync21(filePath, "utf-8");
36659
+ const content = readFileSync22(filePath, "utf-8");
35833
36660
  return JSON.parse(content);
35834
36661
  } catch (err) {
35835
36662
  warn(`[runners] Failed to load config ${filePath}: ${err}`);
@@ -37244,7 +38071,7 @@ var init_telegram2 = __esm({
37244
38071
  });
37245
38072
 
37246
38073
  // src/skills/bootstrap.ts
37247
- 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";
37248
38075
  import { readdir as readdir6, copyFile } from "fs/promises";
37249
38076
  import { join as join34, dirname as dirname6 } from "path";
37250
38077
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -37293,7 +38120,7 @@ function migrateSkillsToThreeTier() {
37293
38120
  }
37294
38121
  if (!skillPath) continue;
37295
38122
  try {
37296
- const raw = readFileSync22(skillPath, "utf-8");
38123
+ const raw = readFileSync23(skillPath, "utf-8");
37297
38124
  const fmMatch = raw.match(/^---\s*\n([\s\S]*?)\n---/);
37298
38125
  if (fmMatch) {
37299
38126
  const fm = fmMatch[1];
@@ -37936,7 +38763,7 @@ var index_exports = {};
37936
38763
  __export(index_exports, {
37937
38764
  main: () => main
37938
38765
  });
37939
- 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";
37940
38767
  import { join as join36 } from "path";
37941
38768
  import dotenv from "dotenv";
37942
38769
  function migrateLayout() {
@@ -37979,7 +38806,7 @@ async function main() {
37979
38806
  let version = "unknown";
37980
38807
  try {
37981
38808
  const pkgPath = new URL("../package.json", import.meta.url);
37982
- version = JSON.parse(readFileSync24(pkgPath, "utf-8")).version;
38809
+ version = JSON.parse(readFileSync25(pkgPath, "utf-8")).version;
37983
38810
  } catch {
37984
38811
  }
37985
38812
  log(`[cc-claw] Starting v${version}`);
@@ -38207,6 +39034,11 @@ ${lines.join("\n")}`;
38207
39034
  log("[cc-claw] Ready!");
38208
39035
  const shutdown = async (signal) => {
38209
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();
38210
39042
  try {
38211
39043
  const { stopAllActiveAgents: stopAllActiveAgents2, stopStaleChatSweep: stopStaleChatSweep2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
38212
39044
  stopAllActiveAgents2();
@@ -38718,13 +39550,13 @@ var init_daemon = __esm({
38718
39550
  });
38719
39551
 
38720
39552
  // src/cli/resolve-chat.ts
38721
- import { readFileSync as readFileSync26 } from "fs";
39553
+ import { readFileSync as readFileSync27 } from "fs";
38722
39554
  function resolveChatId2(globalOpts) {
38723
39555
  const explicit = globalOpts.chat;
38724
39556
  if (explicit) return explicit;
38725
39557
  if (_cachedDefault) return _cachedDefault;
38726
39558
  try {
38727
- const content = readFileSync26(ENV_PATH, "utf-8");
39559
+ const content = readFileSync27(ENV_PATH, "utf-8");
38728
39560
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
38729
39561
  if (match) {
38730
39562
  _cachedDefault = match[1].split(",")[0].trim();
@@ -39186,7 +40018,7 @@ var logs_exports = {};
39186
40018
  __export(logs_exports, {
39187
40019
  logsCommand: () => logsCommand
39188
40020
  });
39189
- 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";
39190
40022
  async function logsCommand(opts) {
39191
40023
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
39192
40024
  if (!existsSync37(logFile)) {
@@ -39194,7 +40026,7 @@ async function logsCommand(opts) {
39194
40026
  process.exit(1);
39195
40027
  }
39196
40028
  const maxLines = parseInt(opts.lines ?? "100", 10);
39197
- const content = readFileSync28(logFile, "utf-8");
40029
+ const content = readFileSync29(logFile, "utf-8");
39198
40030
  const allLines = content.split("\n");
39199
40031
  const tailLines = allLines.slice(-maxLines);
39200
40032
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
@@ -39205,7 +40037,7 @@ async function logsCommand(opts) {
39205
40037
  let lastLength = content.length;
39206
40038
  watchFile2(logFile, { interval: 500 }, () => {
39207
40039
  try {
39208
- const newContent = readFileSync28(logFile, "utf-8");
40040
+ const newContent = readFileSync29(logFile, "utf-8");
39209
40041
  if (newContent.length > lastLength) {
39210
40042
  const newPart = newContent.slice(lastLength);
39211
40043
  process.stdout.write(newPart);
@@ -39237,7 +40069,7 @@ __export(session_logs_exports, {
39237
40069
  sessionLogsList: () => sessionLogsList,
39238
40070
  sessionLogsTail: () => sessionLogsTail
39239
40071
  });
39240
- 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";
39241
40073
  async function sessionLogsList(opts) {
39242
40074
  const logs = listSessionLogs();
39243
40075
  if (logs.length === 0) {
@@ -39295,12 +40127,12 @@ async function sessionLogsTail(opts) {
39295
40127
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
39296
40128
  let lastLength = 0;
39297
40129
  try {
39298
- lastLength = readFileSync29(targetPath, "utf-8").length;
40130
+ lastLength = readFileSync30(targetPath, "utf-8").length;
39299
40131
  } catch {
39300
40132
  }
39301
40133
  watchFile3(targetPath, { interval: 500 }, () => {
39302
40134
  try {
39303
- const content = readFileSync29(targetPath, "utf-8");
40135
+ const content = readFileSync30(targetPath, "utf-8");
39304
40136
  if (content.length > lastLength) {
39305
40137
  process.stdout.write(content.slice(lastLength));
39306
40138
  lastLength = content.length;
@@ -39344,7 +40176,7 @@ __export(gemini_exports, {
39344
40176
  geminiReorder: () => geminiReorder,
39345
40177
  geminiRotation: () => geminiRotation
39346
40178
  });
39347
- 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";
39348
40180
  import { join as join38 } from "path";
39349
40181
  import { createInterface as createInterface8 } from "readline";
39350
40182
  function requireDb() {
@@ -39375,7 +40207,7 @@ function resolveOAuthEmail(configHome) {
39375
40207
  try {
39376
40208
  const accountsPath = join38(configHome, ".gemini", "google_accounts.json");
39377
40209
  if (!existsSync39(accountsPath)) return null;
39378
- const accounts = JSON.parse(readFileSync30(accountsPath, "utf-8"));
40210
+ const accounts = JSON.parse(readFileSync31(accountsPath, "utf-8"));
39379
40211
  return accounts.active || null;
39380
40212
  } catch {
39381
40213
  return null;
@@ -39638,7 +40470,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
39638
40470
  setGeminiSlotEnabled2(slotId, true);
39639
40471
  let accountEmail = slot.label;
39640
40472
  try {
39641
- 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"));
39642
40474
  if (accounts.active) accountEmail = accounts.active;
39643
40475
  } catch {
39644
40476
  }
@@ -39697,7 +40529,7 @@ __export(backend_cmd_factory_exports, {
39697
40529
  makeReorder: () => makeReorder,
39698
40530
  registerBackendSlotCommands: () => registerBackendSlotCommands
39699
40531
  });
39700
- 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";
39701
40533
  import { join as join39 } from "path";
39702
40534
  import { createInterface as createInterface9 } from "readline";
39703
40535
  function requireDb2() {
@@ -40051,7 +40883,7 @@ var init_backend_cmd_factory = __esm({
40051
40883
  const claudeJsonNested = join39(slotDir, ".claude", ".claude.json");
40052
40884
  if (existsSync40(claudeJson)) {
40053
40885
  try {
40054
- const data = JSON.parse(readFileSync31(claudeJson, "utf-8"));
40886
+ const data = JSON.parse(readFileSync32(claudeJson, "utf-8"));
40055
40887
  return Boolean(data.oauthAccount);
40056
40888
  } catch {
40057
40889
  return false;
@@ -40059,7 +40891,7 @@ var init_backend_cmd_factory = __esm({
40059
40891
  }
40060
40892
  if (existsSync40(claudeJsonNested)) {
40061
40893
  try {
40062
- const data = JSON.parse(readFileSync31(claudeJsonNested, "utf-8"));
40894
+ const data = JSON.parse(readFileSync32(claudeJsonNested, "utf-8"));
40063
40895
  return Boolean(data.oauthAccount);
40064
40896
  } catch {
40065
40897
  return false;
@@ -40082,7 +40914,7 @@ var init_backend_cmd_factory = __esm({
40082
40914
  try {
40083
40915
  const claudeJson = join39(slotDir, ".claude.json");
40084
40916
  if (existsSync40(claudeJson)) {
40085
- const data = JSON.parse(readFileSync31(claudeJson, "utf-8"));
40917
+ const data = JSON.parse(readFileSync32(claudeJson, "utf-8"));
40086
40918
  if (data.oauthAccount?.emailAddress) return data.oauthAccount.emailAddress;
40087
40919
  }
40088
40920
  } catch {
@@ -40101,7 +40933,7 @@ var init_backend_cmd_factory = __esm({
40101
40933
  },
40102
40934
  extractLabel: (slotDir) => {
40103
40935
  try {
40104
- const authData = JSON.parse(readFileSync31(join39(slotDir, "auth.json"), "utf-8"));
40936
+ const authData = JSON.parse(readFileSync32(join39(slotDir, "auth.json"), "utf-8"));
40105
40937
  if (authData.email) return authData.email;
40106
40938
  if (authData.account_name) return authData.account_name;
40107
40939
  if (authData.user?.email) return authData.user.email;
@@ -41468,7 +42300,7 @@ __export(config_exports2, {
41468
42300
  configList: () => configList,
41469
42301
  configSet: () => configSet
41470
42302
  });
41471
- import { existsSync as existsSync49, readFileSync as readFileSync32 } from "fs";
42303
+ import { existsSync as existsSync49, readFileSync as readFileSync33 } from "fs";
41472
42304
  async function configList(globalOpts) {
41473
42305
  if (!existsSync49(DB_PATH)) {
41474
42306
  outputError("DB_NOT_FOUND", "Database not found.");
@@ -41554,7 +42386,7 @@ async function configEnv(_globalOpts) {
41554
42386
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
41555
42387
  process.exit(1);
41556
42388
  }
41557
- const content = readFileSync32(ENV_PATH, "utf-8");
42389
+ const content = readFileSync33(ENV_PATH, "utf-8");
41558
42390
  const entries = {};
41559
42391
  const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
41560
42392
  for (const line of content.split("\n")) {
@@ -42550,11 +43382,11 @@ __export(chat_exports2, {
42550
43382
  chatSend: () => chatSend
42551
43383
  });
42552
43384
  import { request as httpRequest2 } from "http";
42553
- import { readFileSync as readFileSync33, existsSync as existsSync59 } from "fs";
43385
+ import { readFileSync as readFileSync34, existsSync as existsSync59 } from "fs";
42554
43386
  function getToken2() {
42555
43387
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
42556
43388
  try {
42557
- if (existsSync59(TOKEN_PATH)) return readFileSync33(TOKEN_PATH, "utf-8").trim();
43389
+ if (existsSync59(TOKEN_PATH)) return readFileSync34(TOKEN_PATH, "utf-8").trim();
42558
43390
  } catch {
42559
43391
  }
42560
43392
  return null;
@@ -43711,7 +44543,7 @@ var init_optimize3 = __esm({
43711
44543
 
43712
44544
  // src/setup.ts
43713
44545
  var setup_exports = {};
43714
- 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";
43715
44547
  import { execFileSync as execFileSync6 } from "child_process";
43716
44548
  import { createInterface as createInterface11 } from "readline";
43717
44549
  import { join as join41 } from "path";
@@ -43803,7 +44635,7 @@ async function setup() {
43803
44635
  if (envSource) {
43804
44636
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
43805
44637
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
43806
- const existing = readFileSync34(envSource, "utf-8");
44638
+ const existing = readFileSync35(envSource, "utf-8");
43807
44639
  for (const line of existing.split("\n")) {
43808
44640
  const match = line.match(/^([^#=]+)=(.*)$/);
43809
44641
  if (match) env[match[1].trim()] = match[2].trim();