cc-claw 0.27.2 → 0.28.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.
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.27.2" : (() => {
36
+ VERSION = true ? "0.28.0" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -62,7 +62,8 @@ __export(paths_exports, {
62
62
  SKILLS_PATH: () => SKILLS_PATH,
63
63
  WHISPER_MODELS_PATH: () => WHISPER_MODELS_PATH,
64
64
  WORKSPACE_PATH: () => WORKSPACE_PATH,
65
- readApiToken: () => readApiToken
65
+ readApiToken: () => readApiToken,
66
+ resolveRealHome: () => resolveRealHome
66
67
  });
67
68
  import { homedir, userInfo } from "os";
68
69
  import { join as join2 } from "path";
@@ -1209,6 +1210,64 @@ var init_store4 = __esm({
1209
1210
  }
1210
1211
  });
1211
1212
 
1213
+ // src/memory/classify.ts
1214
+ function classifyMemory(_trigger, content) {
1215
+ for (const { category, patterns } of CATEGORY_PATTERNS) {
1216
+ for (const pattern of patterns) {
1217
+ if (pattern.test(content)) {
1218
+ return category;
1219
+ }
1220
+ }
1221
+ }
1222
+ return "uncategorized";
1223
+ }
1224
+ var CATEGORY_PATTERNS;
1225
+ var init_classify = __esm({
1226
+ "src/memory/classify.ts"() {
1227
+ "use strict";
1228
+ CATEGORY_PATTERNS = [
1229
+ {
1230
+ category: "decision",
1231
+ patterns: [
1232
+ /\b(decided|chose|picked|selected|opted)\b/i,
1233
+ /\b(switched to|migrated to|moved to|changed to)\b/i,
1234
+ /\b(replaced .+ with|went with|settled on)\b/i,
1235
+ /\b(over|instead of|rather than)\b.*\b(because|due to|since)\b/i
1236
+ ]
1237
+ },
1238
+ {
1239
+ category: "preference",
1240
+ patterns: [
1241
+ /\b(prefer(?:s|red|ring)?)\b/i,
1242
+ /\b(likes?|loved?|enjoys?|hates?|dislikes?)\b/i,
1243
+ /\b(always uses?|never uses?|favorite|favourite)\b/i,
1244
+ /\b(wants? to|doesn't want|rather|instead of)\b/i
1245
+ ]
1246
+ },
1247
+ {
1248
+ category: "event",
1249
+ patterns: [
1250
+ /\b(yesterday|today|this morning|last week|last month)\b/i,
1251
+ /\b(on (monday|tuesday|wednesday|thursday|friday|saturday|sunday))\b/i,
1252
+ /\b(happened|occurred|took place|attended|deployed|launched|released)\b/i,
1253
+ /\b(had a (meeting|call|chat|discussion|session))\b/i
1254
+ ]
1255
+ },
1256
+ {
1257
+ category: "fact",
1258
+ patterns: [
1259
+ /\b(works? at|employed at|job is)\b/i,
1260
+ /\b(lives? in|located in|based in)\b/i,
1261
+ /\b(is a|is an|role is|position is)\b/i,
1262
+ /\b(uses?|runs? on|built with|powered by|written in)\b/i,
1263
+ /\b(name is|called|known as)\b/i,
1264
+ /\b(timezone|time zone)\b/i
1265
+ ]
1266
+ }
1267
+ ];
1268
+ }
1269
+ });
1270
+
1212
1271
  // src/memory/schema.ts
1213
1272
  function initSchema(db3) {
1214
1273
  db3.exec(`
@@ -1628,6 +1687,10 @@ function initSchema(db3) {
1628
1687
  db3.exec(`ALTER TABLE chat_voice ADD COLUMN stt_model TEXT DEFAULT 'small.en'`);
1629
1688
  } catch {
1630
1689
  }
1690
+ try {
1691
+ db3.exec(`ALTER TABLE chat_voice ADD COLUMN stt_echo INTEGER DEFAULT 0`);
1692
+ } catch {
1693
+ }
1631
1694
  db3.exec(`
1632
1695
  CREATE TABLE IF NOT EXISTS backend_limits (
1633
1696
  backend TEXT NOT NULL,
@@ -1754,6 +1817,44 @@ function initSchema(db3) {
1754
1817
  db3.exec("ALTER TABLE session_summaries ADD COLUMN embedding TEXT");
1755
1818
  } catch {
1756
1819
  }
1820
+ try {
1821
+ db3.exec("ALTER TABLE memories ADD COLUMN category TEXT NOT NULL DEFAULT 'uncategorized'");
1822
+ } catch {
1823
+ }
1824
+ try {
1825
+ db3.exec("ALTER TABLE memories ADD COLUMN half_life_days REAL NOT NULL DEFAULT 14.0");
1826
+ } catch {
1827
+ }
1828
+ try {
1829
+ db3.exec("ALTER TABLE memories ADD COLUMN superseded_by INTEGER DEFAULT NULL");
1830
+ } catch {
1831
+ }
1832
+ try {
1833
+ db3.exec("ALTER TABLE memories ADD COLUMN superseded_at TEXT DEFAULT NULL");
1834
+ } catch {
1835
+ }
1836
+ try {
1837
+ const alreadyDone = db3.prepare("SELECT value FROM meta WHERE key = 'schema_category_backfill_done'").get();
1838
+ if (!alreadyDone) {
1839
+ const allUncategorized = db3.prepare("SELECT id, trigger, content FROM memories WHERE category = 'uncategorized'").all();
1840
+ if (allUncategorized.length > 0) {
1841
+ const updateStmt = db3.prepare("UPDATE memories SET category = ? WHERE id = ?");
1842
+ const backfillTxn = db3.transaction(() => {
1843
+ for (const mem of allUncategorized) {
1844
+ const category = classifyMemory(mem.trigger, mem.content);
1845
+ if (category !== "uncategorized") {
1846
+ updateStmt.run(category, mem.id);
1847
+ }
1848
+ }
1849
+ });
1850
+ backfillTxn();
1851
+ log(`[schema] Backfilled categories for ${allUncategorized.length} memories`);
1852
+ }
1853
+ db3.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run("schema_category_backfill_done", "1");
1854
+ }
1855
+ } catch (err) {
1856
+ warn(`[schema] Category backfill skipped: ${err}`);
1857
+ }
1757
1858
  try {
1758
1859
  db3.exec("ALTER TABLE message_log ADD COLUMN message_type TEXT DEFAULT 'text'");
1759
1860
  } catch {
@@ -1975,32 +2076,32 @@ function applySalienceDecay(db3) {
1975
2076
  if (lastDecay) {
1976
2077
  const daysSinceLastDecay = (Date.now() - (/* @__PURE__ */ new Date(lastDecay.replace(" ", "T") + "Z")).getTime()) / (1e3 * 60 * 60 * 24);
1977
2078
  if (daysSinceLastDecay < 0.01) return;
1978
- db3.prepare(`
1979
- UPDATE memories
1980
- SET salience = salience * POWER(0.98, ?)
1981
- WHERE salience >= 0.1
1982
- `).run(daysSinceLastDecay);
1983
2079
  db3.prepare(`
1984
2080
  UPDATE session_summaries
1985
2081
  SET salience = salience * POWER(0.995, ?)
1986
2082
  WHERE salience >= 0.1
1987
2083
  `).run(daysSinceLastDecay);
1988
2084
  } else {
1989
- db3.prepare(`
1990
- UPDATE memories
1991
- SET salience = salience * POWER(0.98, julianday('now') - julianday(last_accessed))
1992
- WHERE salience >= 0.1
1993
- `).run();
1994
2085
  db3.prepare(`
1995
2086
  UPDATE session_summaries
1996
2087
  SET salience = salience * POWER(0.995, julianday('now') - julianday(created_at))
1997
2088
  WHERE salience >= 0.1
1998
2089
  `).run();
1999
2090
  }
2000
- db3.prepare("DELETE FROM memories WHERE salience < 0.1").run();
2001
2091
  db3.prepare("DELETE FROM session_summaries WHERE salience < 0.1").run();
2002
2092
  db3.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('last_salience_decay', ?)").run(now);
2003
2093
  }
2094
+ function getMetaValue(key) {
2095
+ const { getDb: getDb2 } = (init_store5(), __toCommonJS(store_exports5));
2096
+ const db3 = getDb2();
2097
+ const row = db3.prepare("SELECT value FROM meta WHERE key = ?").get(key);
2098
+ return row?.value;
2099
+ }
2100
+ function setMetaValue(key, value) {
2101
+ const { getDb: getDb2 } = (init_store5(), __toCommonJS(store_exports5));
2102
+ const db3 = getDb2();
2103
+ db3.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, value);
2104
+ }
2004
2105
  function backfillJobTitles(database) {
2005
2106
  const rows = database.prepare(
2006
2107
  "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%')"
@@ -2032,6 +2133,8 @@ var init_schema = __esm({
2032
2133
  init_store3();
2033
2134
  init_identity();
2034
2135
  init_store4();
2136
+ init_log();
2137
+ init_classify();
2035
2138
  }
2036
2139
  });
2037
2140
 
@@ -2311,33 +2414,32 @@ var init_embeddings = __esm({
2311
2414
  });
2312
2415
 
2313
2416
  // src/memory/memories.ts
2314
- function queueMemorySalienceBoost(memoryId, boost) {
2315
- const current = pendingMemorySalienceUpdates.get(memoryId) ?? 0;
2316
- pendingMemorySalienceUpdates.set(memoryId, current + boost);
2317
- if (!memorySalienceFlushTimer) {
2318
- memorySalienceFlushTimer = setTimeout(flushMemorySalienceUpdates, 6e4);
2417
+ function queueHalfLifeExtension(memoryId) {
2418
+ pendingHalfLifeExtensions.set(memoryId, (pendingHalfLifeExtensions.get(memoryId) ?? 0) + 1);
2419
+ if (!memoryHalfLifeFlushTimer) {
2420
+ memoryHalfLifeFlushTimer = setTimeout(flushMemoryHalfLifeUpdates, 6e4);
2319
2421
  }
2320
2422
  }
2321
- function flushMemorySalienceUpdates() {
2322
- if (pendingMemorySalienceUpdates.size === 0) {
2323
- memorySalienceFlushTimer = null;
2423
+ function flushMemoryHalfLifeUpdates() {
2424
+ if (pendingHalfLifeExtensions.size === 0) {
2425
+ memoryHalfLifeFlushTimer = null;
2324
2426
  return;
2325
2427
  }
2326
2428
  try {
2327
2429
  const db3 = getDb();
2328
2430
  const stmt = db3.prepare(
2329
- "UPDATE memories SET salience = MAX(0, MIN(1.0, salience + ?)), access_count = access_count + 1, last_accessed = datetime('now') WHERE id = ?"
2431
+ "UPDATE memories SET half_life_days = MIN(90, half_life_days + 2), access_count = access_count + 1, last_accessed = datetime('now') WHERE id = ?"
2330
2432
  );
2331
2433
  const txn = db3.transaction(() => {
2332
- for (const [id, boost] of pendingMemorySalienceUpdates) {
2333
- stmt.run(boost, id);
2434
+ for (const [id] of pendingHalfLifeExtensions) {
2435
+ stmt.run(id);
2334
2436
  }
2335
2437
  });
2336
2438
  txn();
2337
2439
  } catch {
2338
2440
  }
2339
- pendingMemorySalienceUpdates.clear();
2340
- memorySalienceFlushTimer = null;
2441
+ pendingHalfLifeExtensions.clear();
2442
+ memoryHalfLifeFlushTimer = null;
2341
2443
  }
2342
2444
  function invalidateMemoryEmbeddingCache() {
2343
2445
  embeddingCache = null;
@@ -2361,7 +2463,7 @@ function getMemoriesWithoutEmbeddings(limit = 100) {
2361
2463
  }
2362
2464
  function getAllMemoriesWithEmbeddingsUncached() {
2363
2465
  return getDb().prepare(
2364
- "SELECT * FROM memories WHERE embedding IS NOT NULL AND salience >= 0.1"
2466
+ "SELECT * FROM memories WHERE embedding IS NOT NULL AND salience >= 0.1 AND superseded_by IS NULL"
2365
2467
  ).all();
2366
2468
  }
2367
2469
  function getAllMemoriesWithEmbeddings() {
@@ -2373,8 +2475,13 @@ function getAllMemoriesWithEmbeddings() {
2373
2475
  embeddingCache = { data, timestamp: now };
2374
2476
  return data;
2375
2477
  }
2376
- function saveMemoryWithEmbedding(trigger, content, type = "semantic") {
2377
- const id = saveMemory(trigger, content, type);
2478
+ function saveMemoryWithEmbedding(trigger, content, type = "semantic", category = "uncategorized", halfLifeDays = 14) {
2479
+ const db3 = getDb();
2480
+ const result = db3.prepare(
2481
+ "INSERT INTO memories (trigger, content, type, category, half_life_days) VALUES (?, ?, ?, ?, ?)"
2482
+ ).run(trigger, content, type, category, halfLifeDays);
2483
+ const id = Number(result.lastInsertRowid);
2484
+ invalidateMemoryEmbeddingCache();
2378
2485
  const textToEmbed = `${trigger}: ${content}`;
2379
2486
  embedForStorage(textToEmbed).then((emb) => {
2380
2487
  if (emb) {
@@ -2402,33 +2509,45 @@ function searchMemories(queryText, limit = 3) {
2402
2509
  SELECT m.*, fts.rank AS _ftsRank FROM memories m
2403
2510
  JOIN memories_fts fts ON m.id = fts.rowid
2404
2511
  WHERE memories_fts MATCH ?
2512
+ AND m.superseded_by IS NULL
2405
2513
  ORDER BY fts.rank
2406
2514
  LIMIT ?
2407
2515
  `).all(ftsQuery, limit);
2408
2516
  for (const mem of ftsResults) {
2409
- queueMemorySalienceBoost(mem.id, 0.1);
2517
+ queueHalfLifeExtension(mem.id);
2410
2518
  }
2411
2519
  return ftsResults;
2412
2520
  }
2413
2521
  function searchMemoriesReadOnly(readDb, queryText, limit = 3) {
2414
2522
  const ftsQuery = toFts5Query(queryText);
2415
2523
  if (!ftsQuery) return [];
2416
- return readDb.prepare(`
2417
- SELECT m.*, fts.rank AS _ftsRank FROM memories m
2418
- JOIN memories_fts fts ON m.id = fts.rowid
2419
- WHERE memories_fts MATCH ?
2420
- ORDER BY fts.rank
2421
- LIMIT ?
2422
- `).all(ftsQuery, limit);
2524
+ try {
2525
+ return readDb.prepare(`
2526
+ SELECT m.*, fts.rank AS _ftsRank FROM memories m
2527
+ JOIN memories_fts fts ON m.id = fts.rowid
2528
+ WHERE memories_fts MATCH ?
2529
+ AND m.superseded_by IS NULL
2530
+ ORDER BY fts.rank
2531
+ LIMIT ?
2532
+ `).all(ftsQuery, limit);
2533
+ } catch {
2534
+ return readDb.prepare(`
2535
+ SELECT m.*, fts.rank AS _ftsRank FROM memories m
2536
+ JOIN memories_fts fts ON m.id = fts.rowid
2537
+ WHERE memories_fts MATCH ?
2538
+ ORDER BY fts.rank
2539
+ LIMIT ?
2540
+ `).all(ftsQuery, limit);
2541
+ }
2423
2542
  }
2424
2543
  function getRecentMemories(limit = 5) {
2425
2544
  return getDb().prepare(
2426
- "SELECT * FROM memories ORDER BY last_accessed DESC LIMIT ?"
2545
+ "SELECT * FROM memories WHERE superseded_by IS NULL ORDER BY last_accessed DESC LIMIT ?"
2427
2546
  ).all(limit);
2428
2547
  }
2429
2548
  function listMemories() {
2430
2549
  return getDb().prepare(
2431
- "SELECT * FROM memories WHERE salience >= 0.1 ORDER BY salience DESC LIMIT 50"
2550
+ "SELECT * FROM memories WHERE salience >= 0.1 AND superseded_by IS NULL ORDER BY salience DESC LIMIT 50"
2432
2551
  ).all();
2433
2552
  }
2434
2553
  function forgetMemory(keyword) {
@@ -2449,17 +2568,52 @@ function deleteMemoryById(id) {
2449
2568
  }
2450
2569
  function getAllMemoriesForOptimization() {
2451
2570
  return getDb().prepare(
2452
- "SELECT * FROM memories WHERE salience >= 0.1 ORDER BY salience DESC"
2571
+ "SELECT * FROM memories WHERE salience >= 0.1 AND superseded_by IS NULL ORDER BY salience DESC"
2453
2572
  ).all();
2454
2573
  }
2455
2574
  function restoreMemory(memory2) {
2456
- getDb().prepare(`
2457
- INSERT OR REPLACE INTO memories (id, trigger, content, type, salience, access_count, created_at, last_accessed, embedding)
2458
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
2459
- `).run(memory2.id, memory2.trigger, memory2.content, memory2.type, memory2.salience, memory2.access_count, memory2.created_at, memory2.last_accessed, memory2.embedding);
2575
+ const db3 = getDb();
2576
+ db3.prepare(
2577
+ `INSERT OR REPLACE INTO memories
2578
+ (id, trigger, content, type, salience, access_count, created_at, last_accessed, embedding,
2579
+ category, half_life_days, superseded_by, superseded_at)
2580
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
2581
+ ).run(
2582
+ memory2.id,
2583
+ memory2.trigger,
2584
+ memory2.content,
2585
+ memory2.type,
2586
+ memory2.salience,
2587
+ memory2.access_count,
2588
+ memory2.created_at,
2589
+ memory2.last_accessed,
2590
+ memory2.embedding,
2591
+ memory2.category ?? "uncategorized",
2592
+ memory2.half_life_days ?? 14,
2593
+ memory2.superseded_by ?? null,
2594
+ memory2.superseded_at ?? null
2595
+ );
2460
2596
  invalidateMemoryEmbeddingCache();
2461
2597
  }
2462
- var embeddingCache, EMBEDDING_CACHE_TTL_MS, pendingMemorySalienceUpdates, memorySalienceFlushTimer, STOP_WORDS;
2598
+ function markSuperseded(memoryId, supersededById) {
2599
+ if (memoryId === supersededById) return;
2600
+ const db3 = getDb();
2601
+ db3.prepare(
2602
+ `UPDATE memories SET superseded_by = ?, superseded_at = datetime('now') WHERE id = ?`
2603
+ ).run(supersededById, memoryId);
2604
+ }
2605
+ function cleanupSuperseded(gracePeriodDays = 14) {
2606
+ const db3 = getDb();
2607
+ const result = db3.prepare(
2608
+ `DELETE FROM memories WHERE superseded_by IS NOT NULL AND superseded_at < datetime('now', '-' || ? || ' days')`
2609
+ ).run(gracePeriodDays);
2610
+ return result.changes;
2611
+ }
2612
+ function updateMemoryCategory(id, category) {
2613
+ const db3 = getDb();
2614
+ db3.prepare("UPDATE memories SET category = ? WHERE id = ?").run(category, id);
2615
+ }
2616
+ var embeddingCache, EMBEDDING_CACHE_TTL_MS, pendingHalfLifeExtensions, memoryHalfLifeFlushTimer, STOP_WORDS;
2463
2617
  var init_memories = __esm({
2464
2618
  "src/memory/memories.ts"() {
2465
2619
  "use strict";
@@ -2468,8 +2622,8 @@ var init_memories = __esm({
2468
2622
  init_log();
2469
2623
  embeddingCache = null;
2470
2624
  EMBEDDING_CACHE_TTL_MS = 3e4;
2471
- pendingMemorySalienceUpdates = /* @__PURE__ */ new Map();
2472
- memorySalienceFlushTimer = null;
2625
+ pendingHalfLifeExtensions = /* @__PURE__ */ new Map();
2626
+ memoryHalfLifeFlushTimer = null;
2473
2627
  STOP_WORDS = /* @__PURE__ */ new Set([
2474
2628
  "a",
2475
2629
  "an",
@@ -4492,6 +4646,175 @@ var init_api_context = __esm({
4492
4646
  }
4493
4647
  });
4494
4648
 
4649
+ // src/text-utils.ts
4650
+ var text_utils_exports = {};
4651
+ __export(text_utils_exports, {
4652
+ appendTextChunk: () => appendTextChunk,
4653
+ jaccardSimilarity: () => jaccardSimilarity,
4654
+ tokenizeText: () => tokenizeText
4655
+ });
4656
+ function appendTextChunk(accumulated, chunk) {
4657
+ if (!accumulated) return chunk;
4658
+ if (!chunk) return accumulated;
4659
+ return accumulated + chunk;
4660
+ }
4661
+ function tokenizeText(text, minLen = 3) {
4662
+ return new Set(
4663
+ text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length >= minLen)
4664
+ );
4665
+ }
4666
+ function jaccardSimilarity(a, b) {
4667
+ if (a.size === 0 && b.size === 0) return 0;
4668
+ let intersection = 0;
4669
+ for (const term of a) {
4670
+ if (b.has(term)) intersection++;
4671
+ }
4672
+ const union = a.size + b.size - intersection;
4673
+ return union === 0 ? 0 : intersection / union;
4674
+ }
4675
+ var init_text_utils = __esm({
4676
+ "src/text-utils.ts"() {
4677
+ "use strict";
4678
+ }
4679
+ });
4680
+
4681
+ // src/memory/supersede.ts
4682
+ function detectSupersession(newTrigger, newContent, newCategory, existingMemories, newEmbedding) {
4683
+ if (existingMemories.length === 0) return null;
4684
+ const hasChangeSignal = CHANGE_SIGNALS.some((re) => re.test(newContent));
4685
+ const newTerms = tokenizeText(newContent);
4686
+ let bestId = null;
4687
+ let bestScore = 0;
4688
+ for (const mem of existingMemories) {
4689
+ if (mem.superseded_by != null) continue;
4690
+ let score = 0;
4691
+ if (normalizeTrigger(mem.trigger) === normalizeTrigger(newTrigger)) {
4692
+ score += 3;
4693
+ }
4694
+ if (newCategory !== "uncategorized" && mem.category !== "uncategorized") {
4695
+ if (newCategory === mem.category) {
4696
+ score += 1;
4697
+ } else {
4698
+ continue;
4699
+ }
4700
+ }
4701
+ if (newEmbedding && mem.embedding) {
4702
+ try {
4703
+ const memEmbedding = JSON.parse(mem.embedding);
4704
+ const sim = cosineSimilarity(newEmbedding, memEmbedding);
4705
+ if (sim >= EMBEDDING_THRESHOLD) {
4706
+ score += 3;
4707
+ }
4708
+ } catch {
4709
+ }
4710
+ }
4711
+ const memTerms = tokenizeText(mem.content);
4712
+ const overlap = jaccardSimilarity(newTerms, memTerms);
4713
+ if (overlap >= KEYWORD_OVERLAP_THRESHOLD) {
4714
+ score += 2;
4715
+ }
4716
+ if (hasChangeSignal) {
4717
+ score += 2;
4718
+ }
4719
+ if (score > bestScore) {
4720
+ bestScore = score;
4721
+ bestId = mem.id;
4722
+ }
4723
+ }
4724
+ return bestScore >= SUPERSESSION_SCORE_THRESHOLD ? bestId : null;
4725
+ }
4726
+ function normalizeTrigger(trigger) {
4727
+ return trigger.toLowerCase().trim().replace(/[\s_-]+/g, "-");
4728
+ }
4729
+ var CHANGE_SIGNALS, EMBEDDING_THRESHOLD, KEYWORD_OVERLAP_THRESHOLD, SUPERSESSION_SCORE_THRESHOLD;
4730
+ var init_supersede = __esm({
4731
+ "src/memory/supersede.ts"() {
4732
+ "use strict";
4733
+ init_embeddings();
4734
+ init_text_utils();
4735
+ CHANGE_SIGNALS = [
4736
+ /\bswitched to\b/i,
4737
+ /\bchanged to\b/i,
4738
+ /\bnow uses?\b/i,
4739
+ /\bno longer\b/i,
4740
+ /\breplaced\b/i,
4741
+ /\bmigrated to\b/i,
4742
+ /\bmoved to\b/i,
4743
+ /\bstopped using\b/i,
4744
+ /\bupdated to\b/i
4745
+ ];
4746
+ EMBEDDING_THRESHOLD = 0.75;
4747
+ KEYWORD_OVERLAP_THRESHOLD = 0.4;
4748
+ SUPERSESSION_SCORE_THRESHOLD = 4;
4749
+ }
4750
+ });
4751
+
4752
+ // src/memory/engine.ts
4753
+ var engine_exports = {};
4754
+ __export(engine_exports, {
4755
+ cleanup: () => cleanup,
4756
+ forget: () => forget,
4757
+ list: () => list,
4758
+ recall: () => recall,
4759
+ remember: () => remember
4760
+ });
4761
+ async function remember(tag, content, opts = {}) {
4762
+ const category = opts.category ?? classifyMemory(tag, content);
4763
+ const type = opts.type ?? "semantic";
4764
+ const candidates = getAllMemoriesWithEmbeddings();
4765
+ const id = saveMemoryWithEmbedding(tag, content, type, category, DEFAULT_HALF_LIFE);
4766
+ let supersededId = null;
4767
+ try {
4768
+ const queryEmbedding = await embedQuery(content);
4769
+ supersededId = detectSupersession(tag, content, category, candidates, queryEmbedding);
4770
+ if (supersededId != null) {
4771
+ markSuperseded(supersededId, id);
4772
+ log(`[engine] Memory #${supersededId} superseded by #${id} ("${tag}")`);
4773
+ }
4774
+ } catch (err) {
4775
+ log(`[engine] Supersession check skipped: ${err}`);
4776
+ }
4777
+ const result = { id, category };
4778
+ if (supersededId != null) result.superseded = supersededId;
4779
+ return result;
4780
+ }
4781
+ function recall(query, opts = {}) {
4782
+ const limit = opts.limit ?? 5;
4783
+ let results = searchMemories(query, limit);
4784
+ if (opts.category) {
4785
+ results = results.filter((m) => m.category === opts.category);
4786
+ }
4787
+ return results;
4788
+ }
4789
+ function list(_opts = {}) {
4790
+ return listMemories();
4791
+ }
4792
+ function forget(keywordOrId) {
4793
+ if (typeof keywordOrId === "number") {
4794
+ return deleteMemoryById(keywordOrId) ? 1 : 0;
4795
+ }
4796
+ return forgetMemory(keywordOrId);
4797
+ }
4798
+ function cleanup(gracePeriodDays = 14) {
4799
+ const deleted = cleanupSuperseded(gracePeriodDays);
4800
+ if (deleted > 0) {
4801
+ log(`[engine] Cleaned up ${deleted} superseded memories past ${gracePeriodDays}-day grace period`);
4802
+ }
4803
+ return deleted;
4804
+ }
4805
+ var DEFAULT_HALF_LIFE;
4806
+ var init_engine = __esm({
4807
+ "src/memory/engine.ts"() {
4808
+ "use strict";
4809
+ init_memories();
4810
+ init_classify();
4811
+ init_supersede();
4812
+ init_embeddings();
4813
+ init_log();
4814
+ DEFAULT_HALF_LIFE = 14;
4815
+ }
4816
+ });
4817
+
4495
4818
  // src/memory/store.ts
4496
4819
  var store_exports5 = {};
4497
4820
  __export(store_exports5, {
@@ -4508,7 +4831,10 @@ __export(store_exports5, {
4508
4831
  buildApiMessages: () => buildApiMessages,
4509
4832
  cancelJobById: () => cancelJobById,
4510
4833
  checkBackendLimits: () => checkBackendLimits,
4834
+ classifyMemory: () => classifyMemory,
4511
4835
  cleanExpiredWatches: () => cleanExpiredWatches,
4836
+ cleanup: () => cleanup,
4837
+ cleanupSuperseded: () => cleanupSuperseded,
4512
4838
  clearAgentMode: () => clearAgentMode,
4513
4839
  clearAllPaidSlots: () => clearAllPaidSlots,
4514
4840
  clearAllSessions: () => clearAllSessions,
@@ -4529,11 +4855,13 @@ __export(store_exports5, {
4529
4855
  deleteBookmark: () => deleteBookmark,
4530
4856
  deleteMemoryById: () => deleteMemoryById,
4531
4857
  deleteSessionSummary: () => deleteSessionSummary,
4858
+ detectSupersession: () => detectSupersession,
4532
4859
  determineEscalationTarget: () => determineEscalationTarget,
4533
4860
  estimateTokens: () => estimateTokens,
4534
4861
  findBookmarksByPrefix: () => findBookmarksByPrefix,
4535
- flushMemorySalienceUpdates: () => flushMemorySalienceUpdates,
4862
+ flushMemoryHalfLifeUpdates: () => flushMemoryHalfLifeUpdates,
4536
4863
  flushSummarySalienceUpdates: () => flushSummarySalienceUpdates,
4864
+ forget: () => forget,
4537
4865
  forgetMemory: () => forgetMemory,
4538
4866
  getActiveJobs: () => getActiveJobs,
4539
4867
  getActiveWatches: () => getActiveWatches,
@@ -4576,6 +4904,7 @@ __export(store_exports5, {
4576
4904
  getMemoryById: () => getMemoryById,
4577
4905
  getMessagePairById: () => getMessagePairById,
4578
4906
  getMessagePairs: () => getMessagePairs,
4907
+ getMetaValue: () => getMetaValue,
4579
4908
  getMode: () => getMode,
4580
4909
  getModel: () => getModel,
4581
4910
  getModelSignature: () => getModelSignature,
@@ -4606,6 +4935,7 @@ __export(store_exports5, {
4606
4935
  insertJobRun: () => insertJobRun,
4607
4936
  invalidateMemoryEmbeddingCache: () => invalidateMemoryEmbeddingCache,
4608
4937
  invalidateSummaryEmbeddingCache: () => invalidateSummaryEmbeddingCache,
4938
+ list: () => list,
4609
4939
  listMemories: () => listMemories,
4610
4940
  listSessionSummaries: () => listSessionSummaries,
4611
4941
  markBackendSlotExhausted: () => markBackendSlotExhausted,
@@ -4613,14 +4943,18 @@ __export(store_exports5, {
4613
4943
  markMessageLogSummarized: () => markMessageLogSummarized,
4614
4944
  markSlotExhausted: () => markSlotExhausted,
4615
4945
  markSlotSuccess: () => markSlotSuccess,
4946
+ markSuperseded: () => markSuperseded,
4616
4947
  openDatabaseReadOnly: () => openDatabaseReadOnly,
4617
4948
  parseHeartbeatFallbacks: () => parseHeartbeatFallbacks,
4618
4949
  pinChatBackendSlot: () => pinChatBackendSlot,
4619
4950
  pinChatGeminiSlot: () => pinChatGeminiSlot,
4620
4951
  pruneJobRuns: () => pruneJobRuns,
4621
4952
  pruneMessageLog: () => pruneMessageLog,
4953
+ queueHalfLifeExtension: () => queueHalfLifeExtension,
4954
+ recall: () => recall,
4622
4955
  reenableBackendSlot: () => reenableBackendSlot,
4623
4956
  reenableSlot: () => reenableSlot,
4957
+ remember: () => remember,
4624
4958
  removeApiModel: () => removeApiModel,
4625
4959
  removeApiModelById: () => removeApiModelById,
4626
4960
  removeBackendSlot: () => removeBackendSlot,
@@ -4659,6 +4993,7 @@ __export(store_exports5, {
4659
4993
  setGeminiSlotEnabled: () => setGeminiSlotEnabled,
4660
4994
  setGlobalSummarizer: () => setGlobalSummarizer,
4661
4995
  setHeartbeatConfig: () => setHeartbeatConfig,
4996
+ setMetaValue: () => setMetaValue,
4662
4997
  setMode: () => setMode,
4663
4998
  setModel: () => setModel,
4664
4999
  setModelSignature: () => setModelSignature,
@@ -4683,6 +5018,7 @@ __export(store_exports5, {
4683
5018
  updateJobEnabled: () => updateJobEnabled,
4684
5019
  updateJobLastRun: () => updateJobLastRun,
4685
5020
  updateJobNextRun: () => updateJobNextRun,
5021
+ updateMemoryCategory: () => updateMemoryCategory,
4686
5022
  updateMemoryEmbedding: () => updateMemoryEmbedding,
4687
5023
  updateSessionSummaryEmbedding: () => updateSessionSummaryEmbedding,
4688
5024
  upsertBookmark: () => upsertBookmark
@@ -4727,6 +5063,10 @@ var init_store5 = __esm({
4727
5063
  init_backend_slots();
4728
5064
  init_api_models();
4729
5065
  init_api_context();
5066
+ init_schema();
5067
+ init_classify();
5068
+ init_supersede();
5069
+ init_engine();
4730
5070
  _testDbPath = null;
4731
5071
  }
4732
5072
  });
@@ -4945,6 +5285,11 @@ var init_resolve_executable = __esm({
4945
5285
  });
4946
5286
 
4947
5287
  // src/backends/claude.ts
5288
+ var claude_exports = {};
5289
+ __export(claude_exports, {
5290
+ ClaudeAdapter: () => ClaudeAdapter,
5291
+ tryRefreshOAuthSlot: () => tryRefreshOAuthSlot
5292
+ });
4948
5293
  import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync } from "fs";
4949
5294
  import { execFile as execFileCb, spawnSync as spawnSync2 } from "child_process";
4950
5295
  import { promisify } from "util";
@@ -5021,6 +5366,14 @@ async function getValidOAuthToken(configHome) {
5021
5366
  }
5022
5367
  return accessToken;
5023
5368
  }
5369
+ async function tryRefreshOAuthSlot(slot) {
5370
+ if (slot.slotType !== "oauth" || !slot.configHome) return false;
5371
+ const credsPath = join4(slot.configHome, ".claude", ".credentials.json");
5372
+ const oauth = readOAuthCredentials(credsPath);
5373
+ if (!oauth?.refreshToken) return false;
5374
+ const fresh = await refreshOAuthToken(credsPath, oauth.refreshToken, oauth.scopes);
5375
+ return fresh !== null;
5376
+ }
5024
5377
  var execFileAsync, OAUTH_TOKEN_ENDPOINT, OAUTH_CLIENT_ID, REFRESH_THRESHOLD_MS, REFRESH_COOLDOWN_MS, lastRefreshAttempt, ADAPTIVE_MODELS, ClaudeAdapter;
5025
5378
  var init_claude = __esm({
5026
5379
  "src/backends/claude.ts"() {
@@ -6446,7 +6799,8 @@ function isSensitivePath(filePath) {
6446
6799
  if (SENSITIVE_EXACT.has(normalized)) return true;
6447
6800
  return SENSITIVE_PATTERNS.some((pattern) => pattern.test(normalized));
6448
6801
  }
6449
- function createRestrictedBashTool(chatId, permMode, whitelist) {
6802
+ function createRestrictedBashTool(chatId, permMode, opts) {
6803
+ const { whitelist, shellTimeoutMs } = opts ?? {};
6450
6804
  const isYolo = permMode === "yolo";
6451
6805
  return tool({
6452
6806
  description: isYolo ? `Run any shell command. Use this for ALL external CLI tools. No restrictions. Format: restrictedBash({command: 'gsearch "query" --type news'})` : "Run a whitelisted shell command. Use this for ANY external CLI tool \u2014 do NOT call CLIs as separate tools by name, always route them through restrictedBash. Format: restrictedBash({command: 'toolname args...'})",
@@ -6460,12 +6814,18 @@ function createRestrictedBashTool(chatId, permMode, whitelist) {
6460
6814
  return { error: "Empty command" };
6461
6815
  }
6462
6816
  if (!isYolo) {
6463
- const list = whitelist ?? getApiCliWhitelist(chatId);
6464
- const entry = list.find((w) => w.cli === firstWord);
6817
+ const list2 = whitelist ?? getApiCliWhitelist(chatId);
6818
+ const entry = list2.find((w) => w.cli === firstWord);
6465
6819
  if (!entry) {
6466
- const allowed = list.map((w) => w.cli).join(", ") || "(none)";
6820
+ const allowed = list2.map((w) => w.cli).join(", ") || "(none)";
6467
6821
  return {
6468
- error: `Command "${firstWord}" is not whitelisted. Allowed commands: ${allowed}. IMPORTANT: Stop retrying. Tell the user exactly this: "I tried to run '${firstWord}' but it's not in your approved tools list. Go to /tools \u2192 Add Command \u2192 type '${firstWord}' to allow it, then ask me again."`
6822
+ blockedCommand: firstWord,
6823
+ allowedCommands: allowed,
6824
+ userMessage: `\u274C \`${firstWord}\` is not in your approved tools list.
6825
+ Allowed: ${allowed}
6826
+
6827
+ To fix: go to /tools \u2192 Add Command \u2192 type \`${firstWord}\``,
6828
+ instruction: "STOP. Do not retry. Reply to the user with ONLY the userMessage field above, verbatim. Do not add explanation."
6469
6829
  };
6470
6830
  }
6471
6831
  const dangerousPatterns = /[;|&`$(){}]|>>|<<|\n/;
@@ -6474,13 +6834,18 @@ function createRestrictedBashTool(chatId, permMode, whitelist) {
6474
6834
  }
6475
6835
  }
6476
6836
  log(`[api-tools] Executing whitelisted command: ${trimmed.slice(0, 100)}`);
6837
+ const effectiveTimeoutMs = shellTimeoutMs ?? RESTRICTED_BASH_TIMEOUT_MS;
6477
6838
  try {
6478
- const result = await executeShell(trimmed, { timeoutMs: RESTRICTED_BASH_TIMEOUT_MS });
6839
+ const result = await executeShell(trimmed, { timeoutMs: effectiveTimeoutMs });
6479
6840
  return result;
6480
6841
  } catch (err) {
6481
6842
  const msg = err instanceof Error ? err.message : String(err);
6482
6843
  warn(`[api-tools] Shell exec failed: ${msg}`);
6483
- return { error: `Command failed: ${msg}` };
6844
+ const isTimeout = msg.includes("timed out");
6845
+ return isTimeout ? {
6846
+ error: `Command timed out after ${Math.round(effectiveTimeoutMs / 1e3)}s`,
6847
+ suggestion: "Tell the user the command took too long and timed out. They can increase the job timeout via /editjob."
6848
+ } : { error: `Command failed: ${msg}` };
6484
6849
  }
6485
6850
  }
6486
6851
  });
@@ -6642,7 +7007,7 @@ function createWebSearchTool() {
6642
7007
  }
6643
7008
  });
6644
7009
  }
6645
- function buildApiTools(chatId, permMode, mcpTools, webSearchEnabled) {
7010
+ function buildApiTools(chatId, permMode, mcpTools, webSearchEnabled, shellTimeoutMs) {
6646
7011
  const searchEnabled = webSearchEnabled ?? getApiWebSearchEnabled(chatId);
6647
7012
  const readOnly = {
6648
7013
  readFile: createReadFileTool(),
@@ -6654,14 +7019,14 @@ function buildApiTools(chatId, permMode, mcpTools, webSearchEnabled) {
6654
7019
  }
6655
7020
  switch (permMode) {
6656
7021
  case "yolo": {
6657
- const all = { ...readOnly, ...mcpTools ?? {}, restrictedBash: createRestrictedBashTool(chatId, permMode) };
7022
+ const all = { ...readOnly, ...mcpTools ?? {}, restrictedBash: createRestrictedBashTool(chatId, permMode, { shellTimeoutMs }) };
6658
7023
  return all;
6659
7024
  }
6660
7025
  case "safe": {
6661
7026
  const whitelist = getApiCliWhitelist(chatId);
6662
7027
  const base = { ...readOnly, ...mcpTools ?? {} };
6663
7028
  if (whitelist.length === 0) return base;
6664
- return { ...base, restrictedBash: createRestrictedBashTool(chatId, permMode, whitelist) };
7029
+ return { ...base, restrictedBash: createRestrictedBashTool(chatId, permMode, { whitelist, shellTimeoutMs }) };
6665
7030
  }
6666
7031
  case "plan":
6667
7032
  return readOnly;
@@ -6710,7 +7075,7 @@ var init_api_tools = __esm({
6710
7075
  normalize(resolve(DATA_PATH, "api-token")),
6711
7076
  normalize(resolve(CC_CLAW_HOME, ".env"))
6712
7077
  ]);
6713
- RESTRICTED_BASH_TIMEOUT_MS = 12e4;
7078
+ RESTRICTED_BASH_TIMEOUT_MS = 3e5;
6714
7079
  MAX_READ_BYTES = 1e5;
6715
7080
  lastDdgSearchAt = 0;
6716
7081
  DDG_MIN_GAP_MS = 3e3;
@@ -7571,7 +7936,7 @@ function buildContextPrefix(msg) {
7571
7936
  }
7572
7937
  async function sendOrEditKeyboard(chatId, channel, messageId, text, buttons) {
7573
7938
  if (messageId && typeof channel.editKeyboard === "function") {
7574
- const ok = await channel.editKeyboard(chatId, messageId, text, buttons);
7939
+ const ok = await channel.editKeyboard(chatId, messageId, text, buttons, { priority: true });
7575
7940
  if (ok) return messageId;
7576
7941
  }
7577
7942
  if (typeof channel.sendKeyboard === "function") {
@@ -7767,6 +8132,21 @@ You have access to cc-claw orchestrator tools via MCP:
7767
8132
  });
7768
8133
 
7769
8134
  // src/bootstrap/templates/shared.ts
8135
+ function buildCliEnvironment() {
8136
+ return [
8137
+ "## CLI Execution Environment",
8138
+ "",
8139
+ `Your user's real HOME directory is: \`${REAL_HOME}\``,
8140
+ "",
8141
+ "CC-Claw may redirect HOME for credential isolation when spawning backends.",
8142
+ "If you execute any CLI tool (cc-claw, claude, gemini, codex, python3, npm, etc.),",
8143
+ "always prefix the command with the real HOME:",
8144
+ "",
8145
+ `\`HOME=${REAL_HOME} <command>\``,
8146
+ "",
8147
+ "Without this, CLI tools will fail to find their configurations or prompt for login."
8148
+ ].join("\n");
8149
+ }
7770
8150
  function getCurrentDate() {
7771
8151
  return (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA");
7772
8152
  }
@@ -7849,10 +8229,12 @@ function buildHandoffContract() {
7849
8229
  "**Open questions:** Anything unresolved or needing the main agent's attention."
7850
8230
  ].join("\n");
7851
8231
  }
7852
- var TASK_WORKER_SKILL_BUDGET;
8232
+ var REAL_HOME, TASK_WORKER_SKILL_BUDGET;
7853
8233
  var init_shared = __esm({
7854
8234
  "src/bootstrap/templates/shared.ts"() {
7855
8235
  "use strict";
8236
+ init_paths();
8237
+ REAL_HOME = resolveRealHome();
7856
8238
  TASK_WORKER_SKILL_BUDGET = 12e3;
7857
8239
  }
7858
8240
  });
@@ -8737,15 +9119,15 @@ import { existsSync as existsSync12 } from "fs";
8737
9119
  async function withRunnerLock(runnerId, fn) {
8738
9120
  const prev = runnerLocks.get(runnerId) ?? Promise.resolve();
8739
9121
  const next = prev.then(fn, () => fn());
8740
- const cleanup = next.then(
9122
+ const cleanup2 = next.then(
8741
9123
  () => {
8742
- if (runnerLocks.get(runnerId) === cleanup) runnerLocks.delete(runnerId);
9124
+ if (runnerLocks.get(runnerId) === cleanup2) runnerLocks.delete(runnerId);
8743
9125
  },
8744
9126
  () => {
8745
- if (runnerLocks.get(runnerId) === cleanup) runnerLocks.delete(runnerId);
9127
+ if (runnerLocks.get(runnerId) === cleanup2) runnerLocks.delete(runnerId);
8746
9128
  }
8747
9129
  );
8748
- runnerLocks.set(runnerId, cleanup);
9130
+ runnerLocks.set(runnerId, cleanup2);
8749
9131
  return next;
8750
9132
  }
8751
9133
  function safeParseJsonArray(json) {
@@ -10060,6 +10442,9 @@ var init_ndjson = __esm({
10060
10442
  });
10061
10443
 
10062
10444
  // src/memory/inject.ts
10445
+ function memoryDecayRate(halfLifeDays) {
10446
+ return 1 - Math.pow(0.5, 1 / halfLifeDays);
10447
+ }
10063
10448
  function getTopK(query) {
10064
10449
  const wordCount2 = query.split(/\s+/).length;
10065
10450
  const scale = wordCount2 <= 3 ? 0.5 : wordCount2 <= 8 ? 0.75 : 1;
@@ -10094,13 +10479,14 @@ function vectorSearch(queryEmbedding, items, topK) {
10094
10479
  scored.sort((a, b) => b.score - a.score);
10095
10480
  return scored.slice(0, topK);
10096
10481
  }
10097
- function mergeAndScore(allItems, vectorScores, ftsScores, getDays, decayRate, topK) {
10482
+ function mergeAndScore(allItems, vectorScores, ftsScores, getDays, getDecayRate, topK) {
10098
10483
  const vw = getVectorWeight();
10099
10484
  const results = [];
10100
10485
  for (const [id, item] of allItems) {
10101
10486
  const vs = vectorScores.get(id) ?? 0;
10102
10487
  const fs = ftsScores.get(id) ?? 0;
10103
10488
  if (vs === 0 && fs === 0) continue;
10489
+ const decayRate = typeof getDecayRate === "function" ? getDecayRate(item) : getDecayRate;
10104
10490
  const score = hybridScore({
10105
10491
  id,
10106
10492
  vectorScore: vs,
@@ -10122,10 +10508,11 @@ function consumeContextBridge(chatId) {
10122
10508
  pendingContextBridges.delete(chatId);
10123
10509
  return bridge;
10124
10510
  }
10125
- function buildContextBridge(chatId, pairs = 15) {
10511
+ function buildContextBridge(chatId, pairs = 15, interrupted = false) {
10126
10512
  const rows = getRecentMessageLog(chatId, pairs * 2).reverse();
10127
10513
  if (rows.length === 0) return null;
10128
- const lines = [`[Conversation history \u2014 continued from previous session]`];
10514
+ const header2 = interrupted ? `[Previous session was stopped by the user mid-task \u2014 for context only]` : `[Conversation history \u2014 continued from previous session]`;
10515
+ const lines = [header2];
10129
10516
  let totalChars = lines[0].length;
10130
10517
  for (const row of rows) {
10131
10518
  const backendLabel = row.backend ?? "unknown";
@@ -10143,7 +10530,8 @@ function buildContextBridge(chatId, pairs = 15) {
10143
10530
  lines.push(line);
10144
10531
  totalChars += line.length;
10145
10532
  }
10146
- lines.push(`[End of recent history \u2014 you are continuing this conversation]`);
10533
+ const footer = interrupted ? `[End of stopped session \u2014 do NOT continue the previous approach; wait for the user's new instruction]` : `[End of recent history \u2014 you are continuing this conversation]`;
10534
+ lines.push(footer);
10147
10535
  return lines.join("\n");
10148
10536
  }
10149
10537
  function formatToolSummary(toolName, toolInput, toolOutput) {
@@ -10209,7 +10597,7 @@ async function injectMemoryContext(userMessage, chatId) {
10209
10597
  memVectorScores,
10210
10598
  memFtsScores,
10211
10599
  (m) => daysSince(m.last_accessed),
10212
- MEMORY_DECAY_RATE,
10600
+ (m) => memoryDecayRate(m.half_life_days ?? 14),
10213
10601
  FINAL_TOP_K_MEMORIES
10214
10602
  );
10215
10603
  const topSessions = mergeAndScore(
@@ -10235,10 +10623,13 @@ async function injectMemoryContext(userMessage, chatId) {
10235
10623
  }
10236
10624
  combinedSessions = ftsSessions.slice(0, FINAL_TOP_K_SESSIONS);
10237
10625
  }
10626
+ for (const mem of combinedMemories) {
10627
+ queueHalfLifeExtension(mem.id);
10628
+ }
10238
10629
  if (combinedMemories.length === 0 && combinedSessions.length === 0) return null;
10239
10630
  const lines = [];
10240
10631
  for (const m of combinedMemories) {
10241
- let text = `- [${m.type}] ${m.trigger}: ${m.content}`;
10632
+ let text = `- [${m.category}] ${m.trigger}: ${m.content}`;
10242
10633
  if (text.length > MAX_MEMORY_CHARS) text = text.slice(0, MAX_MEMORY_CHARS) + "\u2026";
10243
10634
  lines.push(text);
10244
10635
  }
@@ -10252,13 +10643,12 @@ async function injectMemoryContext(userMessage, chatId) {
10252
10643
  ${lines.join("\n")}
10253
10644
  [End memory context]`;
10254
10645
  }
10255
- var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, BASE_VECTOR_TOP_K, BASE_FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS, MAX_BRIDGE_CHARS, pendingContextBridges, TOOL_SUMMARY_MAX_CHARS;
10646
+ var SESSION_DECAY_RATE, BASE_VECTOR_TOP_K, BASE_FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS, MAX_BRIDGE_CHARS, pendingContextBridges, TOOL_SUMMARY_MAX_CHARS;
10256
10647
  var init_inject = __esm({
10257
10648
  "src/memory/inject.ts"() {
10258
10649
  "use strict";
10259
10650
  init_store5();
10260
10651
  init_embeddings();
10261
- MEMORY_DECAY_RATE = parseFloat(process.env.CC_CLAW_MEMORY_DECAY_RATE ?? "0.02");
10262
10652
  SESSION_DECAY_RATE = parseFloat(process.env.CC_CLAW_SESSION_DECAY_RATE ?? "0.005");
10263
10653
  BASE_VECTOR_TOP_K = 20;
10264
10654
  BASE_FTS_TOP_K = 20;
@@ -10448,6 +10838,7 @@ ${buildToolUsageRules()}
10448
10838
 
10449
10839
  <platform_reference>
10450
10840
  ${buildSystemCapabilities()}
10841
+ ${buildCliEnvironment()}
10451
10842
  ${buildPlatformQuickReference()}
10452
10843
  </platform_reference>
10453
10844
 
@@ -10493,6 +10884,8 @@ ${user}
10493
10884
 
10494
10885
  ${buildSystemCapabilities()}
10495
10886
 
10887
+ ${buildCliEnvironment()}
10888
+
10496
10889
  ${buildPlatformQuickReference()}
10497
10890
 
10498
10891
  ${buildToolUsageRules()}
@@ -10588,6 +10981,7 @@ ${buildDatabaseSafetyBoundary()}
10588
10981
 
10589
10982
  <platform_reference>
10590
10983
  ${buildSystemCapabilities()}
10984
+ ${buildCliEnvironment()}
10591
10985
  ${buildPlatformQuickReference()}
10592
10986
  </platform_reference>`;
10593
10987
  }
@@ -10636,6 +11030,7 @@ ${buildDatabaseSafetyBoundary()}
10636
11030
 
10637
11031
  <platform_reference>
10638
11032
  ${buildSystemCapabilities()}
11033
+ ${buildCliEnvironment()}
10639
11034
  ${buildPlatformQuickReference()}
10640
11035
  </platform_reference>
10641
11036
 
@@ -10676,6 +11071,10 @@ impossible scenarios.
10676
11071
  </avoid_overengineering>
10677
11072
  </tool_guidance>
10678
11073
 
11074
+ <cli_environment>
11075
+ ${buildCliEnvironment()}
11076
+ </cli_environment>
11077
+
10679
11078
  <safety_boundary>
10680
11079
  Take local, reversible actions freely. Confirm before: deleting files, force-pushing,
10681
11080
  amending published commits, posting to external services, or modifying shared infrastructure.
@@ -10742,6 +11141,10 @@ Before finalizing any response, silently check:
10742
11141
  Never invent library APIs, file paths, or documentation. If uncertain, say so.
10743
11142
  </grounding_rules>
10744
11143
 
11144
+ <cli_environment>
11145
+ ${buildCliEnvironment()}
11146
+ </cli_environment>
11147
+
10745
11148
  <safety_boundary>
10746
11149
  Confirm before: deleting files, posting to external services, database schema changes.
10747
11150
  Do not access ~/.cc-claw/data/cc-claw.db directly.
@@ -11011,14 +11414,16 @@ async function assembleBootstrapPrompt(userMessage, entityType = "main", profile
11011
11414
  if (permMode && permMode !== "yolo") {
11012
11415
  sections.push(buildPermissionNotice(permMode));
11013
11416
  }
11014
- if (backendType === "api" && profile !== "minimal") {
11417
+ if (backendType === "api") {
11015
11418
  sections.push(`[API Backend \u2014 Tool Usage]
11016
11419
  You are operating as a direct API model (not a CLI like Claude Code or Gemini CLI).
11017
11420
  External tools (gsearch, pwm, gws, gemcli, nlm, curl, python3, etc.) are accessed ONLY via the \`restrictedBash\` tool.
11018
11421
  NEVER call external CLIs as direct tools by name \u2014 they are not registered as native tools.
11019
11422
  Correct: restrictedBash({"command": "gsearch \\"query\\" --type news"})
11020
11423
  Incorrect: gsearch({"query": "..."}) \u2190 this will fail silently
11021
- If a skill or instruction says to use a CLI tool, always route it through restrictedBash.`);
11424
+ If a skill or instruction says to use a CLI tool, always route it through restrictedBash.
11425
+
11426
+ ${buildCliEnvironment()}`);
11022
11427
  }
11023
11428
  if (responseStyle) {
11024
11429
  if (responseStyle === "concise") {
@@ -11185,6 +11590,12 @@ Use cc_claw_memory for ALL memory operations. Do NOT use your native memory syst
11185
11590
  - To search: cc_claw_memory(action: "recall", query: "...")
11186
11591
  - To list: cc_claw_memory(action: "list")
11187
11592
  - To search history: cc_claw_memory(action: "history", query: "...")
11593
+ When using cc_claw_memory remember, include the category parameter when the memory type is clear:
11594
+ - "fact" \u2014 biographical or situational truths (name, role, location, tools used, tech stack)
11595
+ - "preference" \u2014 likes, dislikes, habits, working style, stated preferences
11596
+ - "event" \u2014 something that happened at a specific time (meetings, deployments, milestones)
11597
+ - "decision" \u2014 a deliberate choice with reasoning (architecture choices, tool selections)
11598
+ Omit category if uncertain \u2014 the system will auto-classify.
11188
11599
  For scheduling: cc_claw_schedule(action: "create", schedule: "...", task: "...")
11189
11600
  If an action is not available via MCP tools, fall back to the cc-claw CLI.`;
11190
11601
  if (agentMode === "claw") {
@@ -11220,6 +11631,7 @@ var init_loader2 = __esm({
11220
11631
  init_paths();
11221
11632
  init_inject();
11222
11633
  init_init();
11634
+ init_shared();
11223
11635
  init_log();
11224
11636
  init_store3();
11225
11637
  init_store5();
@@ -11258,22 +11670,6 @@ var init_types3 = __esm({
11258
11670
  }
11259
11671
  });
11260
11672
 
11261
- // src/text-utils.ts
11262
- var text_utils_exports = {};
11263
- __export(text_utils_exports, {
11264
- appendTextChunk: () => appendTextChunk
11265
- });
11266
- function appendTextChunk(accumulated, chunk) {
11267
- if (!accumulated) return chunk;
11268
- if (!chunk) return accumulated;
11269
- return accumulated + chunk;
11270
- }
11271
- var init_text_utils = __esm({
11272
- "src/text-utils.ts"() {
11273
- "use strict";
11274
- }
11275
- });
11276
-
11277
11673
  // src/services/ollama/client.ts
11278
11674
  var client_exports = {};
11279
11675
  __export(client_exports, {
@@ -13567,6 +13963,7 @@ async function spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSess
13567
13963
  }
13568
13964
  const maxAttempts = slots.length;
13569
13965
  let lastError;
13966
+ const oauthRefreshRetried = /* @__PURE__ */ new Set();
13570
13967
  for (let i = 0; i < maxAttempts; i++) {
13571
13968
  const slot = getNextBackendSlot(chatId, adapter.id);
13572
13969
  if (!slot) break;
@@ -13584,6 +13981,21 @@ async function spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSess
13584
13981
  if (/rate.?limit|too many requests|429|503|timeout/i.test(errMsg)) {
13585
13982
  throw err;
13586
13983
  }
13984
+ const isOAuthExpiry = slot.slotType === "oauth" && /OAuth token has expired/i.test(errMsg);
13985
+ if (isOAuthExpiry && !oauthRefreshRetried.has(slot.id)) {
13986
+ oauthRefreshRetried.add(slot.id);
13987
+ try {
13988
+ const { tryRefreshOAuthSlot: tryRefreshOAuthSlot2 } = await Promise.resolve().then(() => (init_claude(), claude_exports));
13989
+ const refreshed = await tryRefreshOAuthSlot2(slot);
13990
+ if (refreshed) {
13991
+ log(`[agent:${adapter.id}-rotation] OAuth token refreshed for ${slotLabel} \u2014 retrying`);
13992
+ i--;
13993
+ continue;
13994
+ }
13995
+ } catch {
13996
+ }
13997
+ warn(`[agent:${adapter.id}-rotation] OAuth refresh failed for ${slotLabel} \u2014 rotating`);
13998
+ }
13587
13999
  const isExhausted = /quota|exhausted|exceeded|insufficient|credit|billing|unauthorized|forbidden|401|403/i.test(errMsg);
13588
14000
  if (isExhausted) {
13589
14001
  warn(`[agent:${adapter.id}-rotation] Slot ${slotLabel} exhausted: ${errMsg.slice(0, 200)}`);
@@ -14272,6 +14684,335 @@ var init_format = __esm({
14272
14684
  }
14273
14685
  });
14274
14686
 
14687
+ // src/memory/sweep.ts
14688
+ var sweep_exports = {};
14689
+ __export(sweep_exports, {
14690
+ SWEEP_DEFAULT_CRON: () => SWEEP_DEFAULT_CRON,
14691
+ SWEEP_ENABLED_KEY: () => SWEEP_ENABLED_KEY,
14692
+ SWEEP_JOB_TYPE: () => SWEEP_JOB_TYPE,
14693
+ disableSweep: () => disableSweep,
14694
+ enableSweep: () => enableSweep,
14695
+ findSweepJob: () => findSweepJob,
14696
+ runWeeklySweep: () => runWeeklySweep
14697
+ });
14698
+ async function runWeeklySweep(chatId, channel, backendId, model2) {
14699
+ log("[sweep] Starting weekly memory sweep");
14700
+ const cleanedUp = cleanup();
14701
+ let suggestionsCount = 0;
14702
+ try {
14703
+ const suggestions = await runMemoryAnalysis(chatId, backendId, model2);
14704
+ suggestionsCount = suggestions.length;
14705
+ if (suggestionsCount === 0 && cleanedUp === 0) {
14706
+ log("[sweep] Memory bank healthy, no action needed");
14707
+ return { suggestionsCount: 0, cleanedUp };
14708
+ }
14709
+ const lines = [
14710
+ "\u{1F9E0} Weekly Memory Health",
14711
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"
14712
+ ];
14713
+ if (cleanedUp > 0) {
14714
+ lines.push(`\u{1F5D1} Cleaned up ${cleanedUp} expired superseded memories`);
14715
+ }
14716
+ if (suggestionsCount > 0) {
14717
+ const counts = {};
14718
+ for (const s of suggestions) {
14719
+ counts[s.type] = (counts[s.type] ?? 0) + 1;
14720
+ }
14721
+ const parts = [];
14722
+ if (counts.superseded) parts.push(`${counts.superseded} contradiction${counts.superseded > 1 ? "s" : ""}`);
14723
+ if (counts.duplicate) parts.push(`${counts.duplicate} duplicate${counts.duplicate > 1 ? "s" : ""}`);
14724
+ if (counts.merge) parts.push(`${counts.merge} merge opportunit${counts.merge > 1 ? "ies" : "y"}`);
14725
+ if (counts.stale) parts.push(`${counts.stale} stale`);
14726
+ if (counts.categorize) parts.push(`${counts.categorize} uncategorized`);
14727
+ if (counts.reclassify) parts.push(`${counts.reclassify} misclassified`);
14728
+ lines.push("", `Found: ${parts.join(", ")}`, "");
14729
+ lines.push("Review these suggestions to keep your memory clean.");
14730
+ } else {
14731
+ lines.push("", "\u2705 Memory bank is healthy!");
14732
+ }
14733
+ const buttons = [];
14734
+ if (suggestionsCount > 0) {
14735
+ buttons.push([{ label: "Review Now", data: "mem:opt:start", style: "success" }]);
14736
+ }
14737
+ buttons.push([{ label: "Dismiss", data: "mem:sweep:dismiss" }]);
14738
+ await sendOrEditKeyboard(chatId, channel, void 0, lines.join("\n"), buttons);
14739
+ return { suggestionsCount, cleanedUp };
14740
+ } catch (err) {
14741
+ const msg = errorMessage(err);
14742
+ warn(`[sweep] Weekly sweep failed: ${msg}`);
14743
+ return { suggestionsCount: 0, cleanedUp, error: msg };
14744
+ }
14745
+ }
14746
+ function findSweepJob() {
14747
+ try {
14748
+ const { getDb: getDb2, getJobById: getJobById3 } = (init_store5(), __toCommonJS(store_exports5));
14749
+ const row = getDb2().prepare(
14750
+ "SELECT id FROM jobs WHERE job_type = ? AND active = 1 ORDER BY id LIMIT 1"
14751
+ ).get(SWEEP_JOB_TYPE);
14752
+ if (!row) return null;
14753
+ return getJobById3(row.id) ?? null;
14754
+ } catch {
14755
+ return null;
14756
+ }
14757
+ }
14758
+ function enableSweep(chatId, opts) {
14759
+ const existing = findSweepJob();
14760
+ const { setMetaValue: setMetaValue2 } = (init_store5(), __toCommonJS(store_exports5));
14761
+ setMetaValue2(SWEEP_ENABLED_KEY, "1");
14762
+ if (existing) {
14763
+ if (!existing.enabled) {
14764
+ const { resumeJob: resumeJob2 } = (init_cron(), __toCommonJS(cron_exports));
14765
+ resumeJob2(existing.id);
14766
+ log(`[sweep] Resumed job #${existing.id}`);
14767
+ }
14768
+ return existing.id;
14769
+ }
14770
+ const { insertJob: insertJob2 } = (init_jobs(), __toCommonJS(jobs_exports));
14771
+ const { startSingleJob: startSingleJob2 } = (init_cron(), __toCommonJS(cron_exports));
14772
+ const cron2 = opts?.cron ?? SWEEP_DEFAULT_CRON;
14773
+ const timezone = opts?.timezone ?? (Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC");
14774
+ const backend2 = opts?.backend ?? resolveDefaultBackend(chatId);
14775
+ const model2 = opts?.model ?? resolveDefaultModel(backend2);
14776
+ const job = insertJob2({
14777
+ scheduleType: "cron",
14778
+ cron: cron2,
14779
+ description: "Weekly memory health sweep \u2014 cleanup and optimization",
14780
+ title: "Weekly Memory Sweep",
14781
+ chatId,
14782
+ backend: backend2,
14783
+ model: model2,
14784
+ sessionType: "isolated",
14785
+ deliveryMode: "none",
14786
+ channel: opts?.channel ?? "telegram",
14787
+ target: opts?.target ?? chatId,
14788
+ timezone,
14789
+ jobType: SWEEP_JOB_TYPE,
14790
+ timeout: 300
14791
+ });
14792
+ startSingleJob2(job);
14793
+ log(`[sweep] Created job #${job.id} (cron="${cron2}", backend=${backend2}, model=${model2})`);
14794
+ return job.id;
14795
+ }
14796
+ function disableSweep() {
14797
+ const { setMetaValue: setMetaValue2 } = (init_store5(), __toCommonJS(store_exports5));
14798
+ setMetaValue2(SWEEP_ENABLED_KEY, "0");
14799
+ const job = findSweepJob();
14800
+ if (!job) return false;
14801
+ const { pauseJob: pauseJob2 } = (init_cron(), __toCommonJS(cron_exports));
14802
+ pauseJob2(job.id);
14803
+ log(`[sweep] Paused job #${job.id}`);
14804
+ return true;
14805
+ }
14806
+ function resolveDefaultBackend(chatId) {
14807
+ try {
14808
+ const { getAdapterForChat: getAdapterForChat2 } = (init_backends(), __toCommonJS(backends_exports));
14809
+ return getAdapterForChat2(chatId).id;
14810
+ } catch {
14811
+ return "claude";
14812
+ }
14813
+ }
14814
+ function resolveDefaultModel(backend2) {
14815
+ try {
14816
+ const { getAdapter: getAdapter3 } = (init_backends(), __toCommonJS(backends_exports));
14817
+ return getAdapter3(backend2).defaultModel;
14818
+ } catch {
14819
+ return "claude-sonnet-4-6";
14820
+ }
14821
+ }
14822
+ var SWEEP_ENABLED_KEY, SWEEP_DEFAULT_CRON, SWEEP_JOB_TYPE;
14823
+ var init_sweep = __esm({
14824
+ "src/memory/sweep.ts"() {
14825
+ "use strict";
14826
+ init_optimize();
14827
+ init_engine();
14828
+ init_log();
14829
+ init_helpers();
14830
+ SWEEP_ENABLED_KEY = "memory_sweep_enabled";
14831
+ SWEEP_DEFAULT_CRON = "0 9 * * 0";
14832
+ SWEEP_JOB_TYPE = "memory_sweep";
14833
+ }
14834
+ });
14835
+
14836
+ // src/router/state.ts
14837
+ var state_exports = {};
14838
+ __export(state_exports, {
14839
+ activeSideQuests: () => activeSideQuests,
14840
+ addForceStoppedChat: () => addForceStoppedChat,
14841
+ bypassBusyCheck: () => bypassBusyCheck,
14842
+ clearHistoryFilter: () => clearHistoryFilter,
14843
+ clearPendingCliAddition: () => clearPendingCliAddition,
14844
+ clearPendingModelResults: () => clearPendingModelResults,
14845
+ clearPendingModelSearch: () => clearPendingModelSearch,
14846
+ consumeForceStoppedChat: () => consumeForceStoppedChat,
14847
+ councilResults: () => councilResults,
14848
+ dashboardClawWarnings: () => dashboardClawWarnings,
14849
+ getActiveSideQuestCount: () => getActiveSideQuestCount,
14850
+ historyFilters: () => historyFilters,
14851
+ parseSideQuestPrefix: () => parseSideQuestPrefix,
14852
+ pendingCliAdditions: () => pendingCliAdditions,
14853
+ pendingInterrupts: () => pendingInterrupts,
14854
+ pendingMcpImports: () => pendingMcpImports,
14855
+ pendingModelResults: () => pendingModelResults,
14856
+ pendingModelSearch: () => pendingModelSearch,
14857
+ pendingNewchatUndo: () => pendingNewchatUndo,
14858
+ pendingSummaryUndo: () => pendingSummaryUndo,
14859
+ setCouncilResult: () => setCouncilResult,
14860
+ setHistoryFilter: () => setHistoryFilter,
14861
+ setPendingCliAddition: () => setPendingCliAddition,
14862
+ setPendingModelResults: () => setPendingModelResults,
14863
+ setPendingModelSearch: () => setPendingModelSearch,
14864
+ startStateSweep: () => startStateSweep,
14865
+ stopAllSideQuests: () => stopAllSideQuests,
14866
+ stopStateSweep: () => stopStateSweep
14867
+ });
14868
+ function addForceStoppedChat(chatId) {
14869
+ _forceStoppedChats.add(chatId);
14870
+ _forceStoppedTimestamps.set(chatId, Date.now());
14871
+ }
14872
+ function consumeForceStoppedChat(chatId) {
14873
+ _forceStoppedTimestamps.delete(chatId);
14874
+ return _forceStoppedChats.delete(chatId);
14875
+ }
14876
+ function setHistoryFilter(chatId, filter) {
14877
+ historyFilters.set(chatId, filter);
14878
+ historyFilterTimestamps.set(chatId, Date.now());
14879
+ }
14880
+ function clearHistoryFilter(chatId) {
14881
+ historyFilters.delete(chatId);
14882
+ historyFilterTimestamps.delete(chatId);
14883
+ }
14884
+ function setCouncilResult(chatId, result) {
14885
+ councilResults.set(chatId, result);
14886
+ councilResultTimestamps.set(chatId, Date.now());
14887
+ }
14888
+ function setPendingModelSearch(chatId, state) {
14889
+ pendingModelSearch.set(chatId, state);
14890
+ pendingModelSearchTimestamps.set(chatId, Date.now());
14891
+ }
14892
+ function clearPendingModelSearch(chatId) {
14893
+ pendingModelSearch.delete(chatId);
14894
+ pendingModelSearchTimestamps.delete(chatId);
14895
+ }
14896
+ function setPendingModelResults(chatId, results) {
14897
+ pendingModelResults.set(chatId, results);
14898
+ }
14899
+ function clearPendingModelResults(chatId) {
14900
+ pendingModelResults.delete(chatId);
14901
+ }
14902
+ function parseSideQuestPrefix(text) {
14903
+ const match = text.match(/^(?:sq|btw):\s*/i);
14904
+ if (match) return { isSideQuest: true, cleanText: text.slice(match[0].length) };
14905
+ return { isSideQuest: false, cleanText: text };
14906
+ }
14907
+ function getActiveSideQuestCount(chatId) {
14908
+ return activeSideQuests.get(chatId)?.size ?? 0;
14909
+ }
14910
+ function stopAllSideQuests(chatId) {
14911
+ const active = activeSideQuests.get(chatId);
14912
+ if (active) {
14913
+ for (const sqId of active) {
14914
+ stopAgent(sqId);
14915
+ }
14916
+ }
14917
+ }
14918
+ function startStateSweep() {
14919
+ if (sweepTimer) return;
14920
+ sweepTimer = setInterval(() => {
14921
+ const now = Date.now();
14922
+ for (const [chatId, ts2] of dashboardClawWarnings) {
14923
+ if (now - ts2 > STALE_THRESHOLD_MS) dashboardClawWarnings.delete(chatId);
14924
+ }
14925
+ for (const [cid, ts2] of historyFilterTimestamps) {
14926
+ if (now - ts2 > STALE_THRESHOLD_MS) {
14927
+ historyFilters.delete(cid);
14928
+ historyFilterTimestamps.delete(cid);
14929
+ }
14930
+ }
14931
+ for (const [cid, ts2] of councilResultTimestamps) {
14932
+ if (now - ts2 > STALE_THRESHOLD_MS) {
14933
+ councilResults.delete(cid);
14934
+ councilResultTimestamps.delete(cid);
14935
+ }
14936
+ }
14937
+ for (const [cid, ts2] of pendingModelSearchTimestamps) {
14938
+ if (now - ts2 > STALE_THRESHOLD_MS) {
14939
+ pendingModelSearch.delete(cid);
14940
+ pendingModelSearchTimestamps.delete(cid);
14941
+ }
14942
+ }
14943
+ for (const chatId of pendingInterrupts.keys()) {
14944
+ if (!_interruptSeen.has(chatId)) {
14945
+ _interruptSeen.add(chatId);
14946
+ } else {
14947
+ pendingInterrupts.delete(chatId);
14948
+ _interruptSeen.delete(chatId);
14949
+ }
14950
+ }
14951
+ for (const chatId of _interruptSeen) {
14952
+ if (!pendingInterrupts.has(chatId)) _interruptSeen.delete(chatId);
14953
+ }
14954
+ for (const [cid, ts2] of pendingCliTimestamps) {
14955
+ if (now - ts2 > STALE_THRESHOLD_MS) {
14956
+ pendingCliAdditions.delete(cid);
14957
+ pendingCliTimestamps.delete(cid);
14958
+ }
14959
+ }
14960
+ for (const [cid, state] of pendingMcpImports) {
14961
+ if (now - state.startedAt > STALE_THRESHOLD_MS) pendingMcpImports.delete(cid);
14962
+ }
14963
+ for (const [cid, ts2] of _forceStoppedTimestamps) {
14964
+ if (now - ts2 > STALE_THRESHOLD_MS) {
14965
+ _forceStoppedChats.delete(cid);
14966
+ _forceStoppedTimestamps.delete(cid);
14967
+ }
14968
+ }
14969
+ }, SWEEP_INTERVAL_MS);
14970
+ sweepTimer.unref();
14971
+ }
14972
+ function stopStateSweep() {
14973
+ if (sweepTimer) {
14974
+ clearInterval(sweepTimer);
14975
+ sweepTimer = null;
14976
+ }
14977
+ }
14978
+ function setPendingCliAddition(chatId, messageId) {
14979
+ pendingCliAdditions.set(chatId, messageId);
14980
+ pendingCliTimestamps.set(chatId, Date.now());
14981
+ }
14982
+ function clearPendingCliAddition(chatId) {
14983
+ pendingCliAdditions.delete(chatId);
14984
+ pendingCliTimestamps.delete(chatId);
14985
+ }
14986
+ var pendingInterrupts, bypassBusyCheck, _forceStoppedChats, _forceStoppedTimestamps, activeSideQuests, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, historyFilters, historyFilterTimestamps, councilResults, councilResultTimestamps, pendingModelSearch, pendingModelSearchTimestamps, pendingModelResults, SWEEP_INTERVAL_MS, STALE_THRESHOLD_MS, sweepTimer, _interruptSeen, pendingMcpImports, pendingCliAdditions, pendingCliTimestamps;
14987
+ var init_state = __esm({
14988
+ "src/router/state.ts"() {
14989
+ "use strict";
14990
+ init_agent();
14991
+ pendingInterrupts = /* @__PURE__ */ new Map();
14992
+ bypassBusyCheck = /* @__PURE__ */ new Set();
14993
+ _forceStoppedChats = /* @__PURE__ */ new Set();
14994
+ _forceStoppedTimestamps = /* @__PURE__ */ new Map();
14995
+ activeSideQuests = /* @__PURE__ */ new Map();
14996
+ dashboardClawWarnings = /* @__PURE__ */ new Map();
14997
+ pendingSummaryUndo = /* @__PURE__ */ new Map();
14998
+ pendingNewchatUndo = /* @__PURE__ */ new Map();
14999
+ historyFilters = /* @__PURE__ */ new Map();
15000
+ historyFilterTimestamps = /* @__PURE__ */ new Map();
15001
+ councilResults = /* @__PURE__ */ new Map();
15002
+ councilResultTimestamps = /* @__PURE__ */ new Map();
15003
+ pendingModelSearch = /* @__PURE__ */ new Map();
15004
+ pendingModelSearchTimestamps = /* @__PURE__ */ new Map();
15005
+ pendingModelResults = /* @__PURE__ */ new Map();
15006
+ SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
15007
+ STALE_THRESHOLD_MS = 30 * 60 * 1e3;
15008
+ sweepTimer = null;
15009
+ _interruptSeen = /* @__PURE__ */ new Set();
15010
+ pendingMcpImports = /* @__PURE__ */ new Map();
15011
+ pendingCliAdditions = /* @__PURE__ */ new Map();
15012
+ pendingCliTimestamps = /* @__PURE__ */ new Map();
15013
+ }
15014
+ });
15015
+
14275
15016
  // src/ui/pagination.ts
14276
15017
  function buildPaginatedKeyboard(opts) {
14277
15018
  const { items, page, callbackPrefix, renderItem, headerText, footerButtons } = opts;
@@ -14897,6 +15638,13 @@ async function promptAccount(chatId, channel) {
14897
15638
  await promptTimeout(chatId, channel);
14898
15639
  return;
14899
15640
  }
15641
+ const adapter = getAdapter(pending.backend);
15642
+ if (adapter.type === "api") {
15643
+ pending.credentialSlotId = null;
15644
+ pending.step = "timeout";
15645
+ await promptTimeout(chatId, channel);
15646
+ return;
15647
+ }
14900
15648
  const isGemini = pending.backend === BACKEND.GEMINI;
14901
15649
  const slots = isGemini ? getGeminiSlots() : getBackendSlots(pending.backend);
14902
15650
  const enabledSlots = slots.filter((s) => s.enabled);
@@ -15158,6 +15906,7 @@ __export(stt_exports, {
15158
15906
  LOCAL_WHISPER_MODELS: () => LOCAL_WHISPER_MODELS,
15159
15907
  MACOS_VOICES: () => MACOS_VOICES,
15160
15908
  downloadWhisperModel: () => downloadWhisperModel,
15909
+ getSttEcho: () => getSttEcho,
15161
15910
  getSttModel: () => getSttModel,
15162
15911
  getSttProvider: () => getSttProvider,
15163
15912
  getVoiceConfig: () => getVoiceConfig,
@@ -15165,10 +15914,12 @@ __export(stt_exports, {
15165
15914
  isVoiceEnabled: () => isVoiceEnabled,
15166
15915
  isWhisperCliAvailable: () => isWhisperCliAvailable,
15167
15916
  isWhisperModelDownloaded: () => isWhisperModelDownloaded,
15917
+ setSttEcho: () => setSttEcho,
15168
15918
  setSttModel: () => setSttModel,
15169
15919
  setSttProvider: () => setSttProvider,
15170
15920
  setVoiceProvider: () => setVoiceProvider,
15171
15921
  synthesizeSpeech: () => synthesizeSpeech,
15922
+ toggleSttEcho: () => toggleSttEcho,
15172
15923
  toggleVoice: () => toggleVoice,
15173
15924
  transcribeAudio: () => transcribeAudio
15174
15925
  });
@@ -15247,6 +15998,23 @@ function setSttModel(chatId, model2) {
15247
15998
  ON CONFLICT(chat_id) DO UPDATE SET stt_model = ?
15248
15999
  `).run(chatId, model2, model2);
15249
16000
  }
16001
+ function getSttEcho(chatId) {
16002
+ const db3 = getDb();
16003
+ const row = db3.prepare("SELECT stt_echo FROM chat_voice WHERE chat_id = ?").get(chatId);
16004
+ return row?.stt_echo === 1;
16005
+ }
16006
+ function setSttEcho(chatId, enabled) {
16007
+ const db3 = getDb();
16008
+ db3.prepare(`
16009
+ INSERT INTO chat_voice (chat_id, enabled, stt_echo) VALUES (?, 0, ?)
16010
+ ON CONFLICT(chat_id) DO UPDATE SET stt_echo = ?
16011
+ `).run(chatId, enabled ? 1 : 0, enabled ? 1 : 0);
16012
+ }
16013
+ function toggleSttEcho(chatId) {
16014
+ const newState = !getSttEcho(chatId);
16015
+ setSttEcho(chatId, newState);
16016
+ return newState;
16017
+ }
15250
16018
  function isFfmpegAvailable() {
15251
16019
  if (ffmpegAvailable !== null) return ffmpegAvailable;
15252
16020
  try {
@@ -16135,8 +16903,8 @@ function enableHeartbeat(chatId, opts) {
16135
16903
  const { insertJob: insertJob2 } = (init_jobs(), __toCommonJS(jobs_exports));
16136
16904
  const { startSingleJob: startSingleJob2 } = (init_cron(), __toCommonJS(cron_exports));
16137
16905
  const intervalMs = opts?.intervalMs ?? DEFAULT_INTERVAL_MS;
16138
- const backend2 = opts?.backend ?? resolveDefaultBackend(chatId);
16139
- const model2 = opts?.model ?? resolveDefaultModel(backend2);
16906
+ const backend2 = opts?.backend ?? resolveDefaultBackend2(chatId);
16907
+ const model2 = opts?.model ?? resolveDefaultModel2(backend2);
16140
16908
  const job = insertJob2({
16141
16909
  scheduleType: "every",
16142
16910
  everyMs: intervalMs,
@@ -16156,7 +16924,7 @@ function enableHeartbeat(chatId, opts) {
16156
16924
  log(`[heartbeat] Created job #${job.id} (every ${intervalMs / 6e4}min, backend=${backend2}, model=${model2})`);
16157
16925
  return job.id;
16158
16926
  }
16159
- function resolveDefaultBackend(chatId) {
16927
+ function resolveDefaultBackend2(chatId) {
16160
16928
  try {
16161
16929
  const { getAdapterForChat: getAdapterForChat2 } = (init_backends(), __toCommonJS(backends_exports));
16162
16930
  return getAdapterForChat2(chatId).id;
@@ -16164,7 +16932,7 @@ function resolveDefaultBackend(chatId) {
16164
16932
  return "claude";
16165
16933
  }
16166
16934
  }
16167
- function resolveDefaultModel(backend2) {
16935
+ function resolveDefaultModel2(backend2) {
16168
16936
  try {
16169
16937
  const { getAdapter: getAdapter3 } = (init_backends(), __toCommonJS(backends_exports));
16170
16938
  return getAdapter3(backend2).defaultModel;
@@ -16173,8 +16941,8 @@ function resolveDefaultModel(backend2) {
16173
16941
  }
16174
16942
  }
16175
16943
  function migrateHeartbeatDefaults(job) {
16176
- const backend2 = resolveDefaultBackend(job.chatId);
16177
- const model2 = resolveDefaultModel(backend2);
16944
+ const backend2 = resolveDefaultBackend2(job.chatId);
16945
+ const model2 = resolveDefaultModel2(backend2);
16178
16946
  try {
16179
16947
  const { getDb: getDb2 } = (init_store5(), __toCommonJS(store_exports5));
16180
16948
  getDb2().prepare("UPDATE jobs SET backend = ?, model = ? WHERE id = ?").run(backend2, model2, job.id);
@@ -16418,10 +17186,10 @@ function formatNightlySummary(insights, totalPending) {
16418
17186
  } else {
16419
17187
  header2 = `Nightly Reflection \u2014 ${newCount} proposal${newCount === 1 ? "" : "s"} ready`;
16420
17188
  }
16421
- const list = insights.map((ins, i) => `\u2022 [${ins.category}] ${ins.insight}`).join("\n");
17189
+ const list2 = insights.map((ins, i) => `\u2022 [${ins.category}] ${ins.insight}`).join("\n");
16422
17190
  return `${header2}
16423
17191
 
16424
- ${list}
17192
+ ${list2}
16425
17193
 
16426
17194
  Review with /evolve`;
16427
17195
  }
@@ -17038,6 +17806,12 @@ async function sendVoiceConfigKeyboard(chatId, channel, messageId) {
17038
17806
  buttons.push(row);
17039
17807
  }
17040
17808
  }
17809
+ const echoOn = getSttEcho(chatId);
17810
+ buttons.push([{
17811
+ label: `${echoOn ? "\u2713 " : ""}\u{1F399} Show Transcription`,
17812
+ data: "vcfg:echo",
17813
+ ...echoOn ? { style: "success" } : {}
17814
+ }]);
17041
17815
  buttons.push([
17042
17816
  {
17043
17817
  label: `${!ttsConfig.enabled ? "\u2713 " : ""}\u{1F507} Replies Off`,
@@ -17330,9 +18104,11 @@ async function sendMemoryPage(chatId, channel, page, messageId) {
17330
18104
  const buttons = [];
17331
18105
  for (const [i, m] of pageItems.entries()) {
17332
18106
  const num = start + i + 1;
17333
- const preview = `${m.trigger}: ${m.content}`.slice(0, 32);
18107
+ const cat = m.category ?? "uncategorized";
18108
+ const full = `[${cat}] ${m.trigger}: ${m.content}`;
18109
+ const preview = full.length > 36 ? `${full.slice(0, 36)}\u2026` : full;
17334
18110
  buttons.push([{
17335
- label: `${num}. ${preview}${m.content.length > 32 ? "\u2026" : ""}`,
18111
+ label: `${num}. ${preview}`,
17336
18112
  data: `mem:view:${m.id}`
17337
18113
  }]);
17338
18114
  }
@@ -17351,6 +18127,12 @@ async function sendMemoryPage(chatId, channel, page, messageId) {
17351
18127
  }
17352
18128
  buttons.push(footerRow);
17353
18129
  buttons.push([{ label: "\u2728 Optimize", data: "mem:opt", style: "success" }]);
18130
+ const sweepEnabled = getMetaValue(SWEEP_ENABLED_KEY) === "1";
18131
+ buttons.push([{
18132
+ label: sweepEnabled ? "\u2713 Weekly Sweep: On" : "Weekly Sweep: Off",
18133
+ data: "mem:sweep:toggle",
18134
+ style: sweepEnabled ? "success" : void 0
18135
+ }]);
17354
18136
  await sendOrEditKeyboard(chatId, channel, messageId, header2, buttons);
17355
18137
  }
17356
18138
  async function sendMemoryDetail(chatId, memoryId, channel, messageId) {
@@ -18155,7 +18937,8 @@ async function sendBackendSwitchConfirmation(chatId, target, channel, messageId)
18155
18937
  }
18156
18938
  async function doBackendSwitch(chatId, backendId, channel, opts) {
18157
18939
  if (!opts?.skipContext) {
18158
- const bridge = buildContextBridge(chatId);
18940
+ const interrupted = consumeForceStoppedChat(chatId);
18941
+ const bridge = buildContextBridge(chatId, 15, interrupted);
18159
18942
  if (bridge) setPendingContextBridge(chatId, bridge);
18160
18943
  }
18161
18944
  clearSession(chatId);
@@ -18235,10 +19018,12 @@ var init_ui = __esm({
18235
19018
  init_format();
18236
19019
  init_backends();
18237
19020
  init_store5();
19021
+ init_sweep();
18238
19022
  init_chat_settings();
18239
19023
  init_api_models();
18240
19024
  init_summarize();
18241
19025
  init_inject();
19026
+ init_state();
18242
19027
  init_session_log();
18243
19028
  init_store3();
18244
19029
  init_log();
@@ -18282,7 +19067,7 @@ function getMemOptSession(chatId) {
18282
19067
  function buildOptimizationPrompt(memories) {
18283
19068
  const lines = memories.map((m, i) => {
18284
19069
  const accessed = m.access_count ?? 0;
18285
- return `[${i + 1}] ID=${m.id} | trigger="${m.trigger}" | content="${m.content}" | type=${m.type} | salience=${m.salience.toFixed(2)} | accessed=${accessed} | created=${m.created_at}`;
19070
+ return `[${i + 1}] ID=${m.id} | trigger="${m.trigger}" | content="${m.content}" | type=${m.type} | category=${m.category} | salience=${m.salience.toFixed(2)} | accessed=${accessed} | half_life=${(m.half_life_days ?? 14).toFixed(1)} | created=${m.created_at}`;
18286
19071
  });
18287
19072
  return ANALYSIS_PROMPT.replace("{MEMORIES}", lines.join("\n"));
18288
19073
  }
@@ -18291,7 +19076,7 @@ function parseOptimizationResponse(raw) {
18291
19076
  const suggestions = [];
18292
19077
  const blocks = raw.split(/^---$/m).filter((b) => b.trim());
18293
19078
  for (const block of blocks) {
18294
- const typeMatch = block.match(/TYPE:\s*(duplicate|merge|stale)/i);
19079
+ const typeMatch = block.match(/TYPE:\s*(duplicate|merge|stale|superseded|categorize|reclassify)/i);
18295
19080
  const idsMatch = block.match(/IDS:\s*([\d,\s]+)/);
18296
19081
  const reasonMatch = block.match(/REASON:\s*(.+?)(?=\n(?:ACTION|SUGGESTION|TYPE|IDS):|$)/s);
18297
19082
  const actionMatch = block.match(/ACTION:\s*(.+)/s);
@@ -18316,6 +19101,21 @@ function parseOptimizationResponse(raw) {
18316
19101
  suggestion.mergedContent = mergeMatch[2].trim();
18317
19102
  }
18318
19103
  }
19104
+ if (type === "superseded") {
19105
+ const supersededMatch = actionRaw.match(/supersede\s+(\d+)\s+by\s+(\d+)/i);
19106
+ if (supersededMatch) {
19107
+ suggestion.memoryIds = [
19108
+ parseInt(supersededMatch[1], 10),
19109
+ parseInt(supersededMatch[2], 10)
19110
+ ].filter((n) => !isNaN(n));
19111
+ }
19112
+ }
19113
+ if (type === "categorize" || type === "reclassify") {
19114
+ const catMatch = actionRaw.match(/(?:categorize|reclassify)\s+\d+\s+as\s+(fact|preference|event|decision)/i);
19115
+ if (catMatch) {
19116
+ suggestion.proposedCategory = catMatch[1].toLowerCase();
19117
+ }
19118
+ }
18319
19119
  suggestions.push(suggestion);
18320
19120
  }
18321
19121
  const seen = /* @__PURE__ */ new Set();
@@ -18365,14 +19165,18 @@ function applySuggestion(suggestion, session2) {
18365
19165
  case "merge": {
18366
19166
  if (!suggestion.mergedTrigger || !suggestion.mergedContent) return;
18367
19167
  const sources = suggestion.memoryIds.map((id) => getMemoryById(id)).filter((m) => m !== void 0);
18368
- const sourceType = sources.length > 0 ? sources.sort((a, b) => b.salience - a.salience)[0].type : "semantic";
19168
+ const topSource = sources.length > 0 ? sources.sort((a, b) => b.salience - a.salience)[0] : null;
19169
+ const sourceType = topSource?.type ?? "semantic";
19170
+ const sourceCategory = topSource?.category;
18369
19171
  for (const id of suggestion.memoryIds) {
18370
19172
  deleteMemoryById(id);
18371
19173
  }
18372
19174
  const newId = saveMemoryWithEmbedding(
18373
19175
  suggestion.mergedTrigger,
18374
19176
  suggestion.mergedContent,
18375
- sourceType
19177
+ sourceType,
19178
+ sourceCategory,
19179
+ 14
18376
19180
  );
18377
19181
  session2.createdIds.push(newId);
18378
19182
  break;
@@ -18383,6 +19187,19 @@ function applySuggestion(suggestion, session2) {
18383
19187
  }
18384
19188
  break;
18385
19189
  }
19190
+ case "superseded": {
19191
+ if (suggestion.memoryIds.length >= 2) {
19192
+ markSuperseded(suggestion.memoryIds[0], suggestion.memoryIds[1]);
19193
+ }
19194
+ break;
19195
+ }
19196
+ case "categorize":
19197
+ case "reclassify": {
19198
+ if (suggestion.proposedCategory && suggestion.memoryIds.length >= 1) {
19199
+ updateMemoryCategory(suggestion.memoryIds[0], suggestion.proposedCategory);
19200
+ }
19201
+ break;
19202
+ }
18386
19203
  }
18387
19204
  }
18388
19205
  function resolveAnalysisAdapter(chatId, backendId, model2) {
@@ -18798,12 +19615,19 @@ Analyze these memories and find optimization opportunities:
18798
19615
 
18799
19616
  1. DUPLICATES: Memories containing essentially the same information (different wording, same meaning). When found, recommend keeping the most complete version.
18800
19617
  2. MERGEABLE: Related memories that would be stronger and more token-efficient as a single combined entry.
18801
- 3. STALE: Memories with very low salience scores (below 0.3) AND zero or near-zero access count \u2014 content that has decayed and is never recalled.
19618
+ 3. STALE: Memories with very low half_life (below 3.0) AND zero access count \u2014 content that has never been recalled and has decayed past usefulness.
19619
+ 4. SUPERSEDED: Memories that contradict each other \u2014 a newer memory makes an older one obsolete. When found, recommend marking the older one as superseded by the newer one.
19620
+ 5. UNCATEGORIZED: Memories with category "uncategorized" that can be classified as fact, preference, event, or decision.
19621
+ 6. RECLASSIFY: Memories whose category appears wrong given their content.
19622
+
19623
+ For superseded: ACTION should be "supersede ID1 by ID2" (ID1=older memory, ID2=newer memory)
19624
+ For uncategorized: ACTION should be "categorize ID1 as <category>"
19625
+ For reclassify: ACTION should be "reclassify ID1 as <category>"
18802
19626
 
18803
19627
  For each suggestion, output EXACTLY this format:
18804
19628
  ---
18805
19629
  SUGGESTION: <short descriptive title>
18806
- TYPE: <duplicate|merge|stale>
19630
+ TYPE: <duplicate|merge|stale|superseded|categorize|reclassify>
18807
19631
  IDS: <comma-separated memory IDs to act on>
18808
19632
  REASON: <1-2 sentence explanation>
18809
19633
  ACTION: <"delete ID1" or "delete ID1, ID2" or "merge into: <new trigger> | <new content>">
@@ -18823,12 +19647,18 @@ Rules:
18823
19647
  TYPE_EMOJI = {
18824
19648
  duplicate: "\u{1F501}",
18825
19649
  merge: "\u{1F500}",
18826
- stale: "\u{1F5D1}"
19650
+ stale: "\u{1F5D1}",
19651
+ superseded: "\u{1F504}",
19652
+ categorize: "\u{1F3F7}",
19653
+ reclassify: "\u{1F3F7}"
18827
19654
  };
18828
19655
  TYPE_LABEL = {
18829
19656
  duplicate: "Duplicate",
18830
19657
  merge: "Merge",
18831
- stale: "Stale"
19658
+ stale: "Stale",
19659
+ superseded: "Superseded",
19660
+ categorize: "Categorize",
19661
+ reclassify: "Reclassify"
18832
19662
  };
18833
19663
  }
18834
19664
  });
@@ -18844,9 +19674,12 @@ var init_memory = __esm({
18844
19674
  try {
18845
19675
  const body = JSON.parse(await readBody(req));
18846
19676
  validateAgentIdentity(req, body);
18847
- const { saveMemoryWithEmbedding: saveMemoryWithEmbedding2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
18848
- const id = saveMemoryWithEmbedding2(body.trigger ?? body.tag, body.content, body.type ?? "semantic");
18849
- jsonResponse(res, { success: true, id });
19677
+ const { remember: remember2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
19678
+ const result = await remember2(body.trigger ?? body.tag, body.content, {
19679
+ category: body.category,
19680
+ type: body.type
19681
+ });
19682
+ jsonResponse(res, { success: true, id: result.id });
18850
19683
  } catch (err) {
18851
19684
  jsonResponse(res, { error: errorMessage(err) }, 400);
18852
19685
  }
@@ -18855,8 +19688,8 @@ var init_memory = __esm({
18855
19688
  try {
18856
19689
  const body = JSON.parse(await readBody(req));
18857
19690
  validateAgentIdentity(req, body);
18858
- const { searchMemories: searchMemories2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
18859
- const results = searchMemories2(body.query, body.limit ?? 5);
19691
+ const { recall: recall2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
19692
+ const results = recall2(body.query, { limit: body.limit ?? 5 });
18860
19693
  jsonResponse(res, { success: true, results });
18861
19694
  } catch (err) {
18862
19695
  jsonResponse(res, { error: errorMessage(err) }, 400);
@@ -18865,8 +19698,8 @@ var init_memory = __esm({
18865
19698
  handleMemoryList = async (_req, res, url) => {
18866
19699
  try {
18867
19700
  const limit = parseInt(url.searchParams.get("limit") ?? "10", 10);
18868
- const { getRecentMemories: getRecentMemories2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
18869
- const results = getRecentMemories2(limit);
19701
+ const { list: list2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
19702
+ const results = list2({ limit });
18870
19703
  jsonResponse(res, { success: true, results });
18871
19704
  } catch (err) {
18872
19705
  jsonResponse(res, { error: errorMessage(err) }, 400);
@@ -18876,14 +19709,13 @@ var init_memory = __esm({
18876
19709
  try {
18877
19710
  const body = JSON.parse(await readBody(req));
18878
19711
  validateAgentIdentity(req, body);
19712
+ const { forget: forget2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
18879
19713
  if (body.memoryId) {
18880
- const { deleteMemoryById: deleteMemoryById3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
18881
- const deleted = deleteMemoryById3(body.memoryId);
18882
- return jsonResponse(res, { success: deleted, mode: "id" });
19714
+ const count = forget2(body.memoryId);
19715
+ return jsonResponse(res, { success: count > 0, mode: "id" });
18883
19716
  }
18884
19717
  if (body.keyword) {
18885
- const { forgetMemory: forgetMemory4 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
18886
- const count = forgetMemory4(body.keyword);
19718
+ const count = forget2(body.keyword);
18887
19719
  return jsonResponse(res, { success: true, count, mode: "keyword" });
18888
19720
  }
18889
19721
  jsonResponse(res, { error: "Either 'keyword' or 'memoryId' is required" }, 400);
@@ -21351,7 +22183,7 @@ var init_api_mcp = __esm({
21351
22183
  });
21352
22184
 
21353
22185
  // src/backends/api-common.ts
21354
- import { streamText, stepCountIs, NoOutputGeneratedError } from "ai";
22186
+ import { streamText, stepCountIs, NoOutputGeneratedError, APICallError } from "ai";
21355
22187
  function toModelMessage(msg) {
21356
22188
  switch (msg.role) {
21357
22189
  case "system":
@@ -21425,7 +22257,7 @@ var init_api_common = __esm({
21425
22257
  } catch (err) {
21426
22258
  log(`[api-common] MCP tools unavailable: ${err instanceof Error ? err.message : String(err)}`);
21427
22259
  }
21428
- const tools2 = buildApiTools(chatId, permMode, mcpTools, getApiWebSearchEnabled(chatId));
22260
+ const tools2 = buildApiTools(chatId, permMode, mcpTools, getApiWebSearchEnabled(chatId), timeoutMs > 0 ? timeoutMs : void 0);
21429
22261
  const hasTools = Object.keys(tools2).length > 0;
21430
22262
  let abortSignal = signal;
21431
22263
  let timeoutHandle;
@@ -21485,6 +22317,9 @@ var init_api_common = __esm({
21485
22317
  const usage2 = await result.usage;
21486
22318
  const providerMeta = await result.providerMetadata;
21487
22319
  const cost = this.extractCost(providerMeta);
22320
+ if (fullText === "" && !usage2.inputTokens) {
22321
+ throw new Error(`${this.backendId}: empty response with no tokens \u2014 possible rate limit or quota exhaustion on free tier`);
22322
+ }
21488
22323
  return {
21489
22324
  text: fullText,
21490
22325
  cost,
@@ -21494,6 +22329,9 @@ var init_api_common = __esm({
21494
22329
  } : void 0
21495
22330
  };
21496
22331
  } catch (err) {
22332
+ if (err instanceof APICallError && err.statusCode === 429) {
22333
+ throw new Error(`${this.backendId}: rate limit (429) \u2014 free tier quota may be exhausted. Retry later or switch to a paid model.`);
22334
+ }
21497
22335
  if (abortSignal?.aborted || signal?.aborted || err instanceof Error && (err.name === "AbortError" || err.message.toLowerCase().includes("abort") || err.message.toLowerCase().includes("cancel"))) {
21498
22336
  return { text: "", cost: null };
21499
22337
  }
@@ -22158,14 +22996,14 @@ var init_delivery = __esm({
22158
22996
  });
22159
22997
 
22160
22998
  // src/scheduler/retry.ts
22161
- import { APICallError } from "ai";
22999
+ import { APICallError as APICallError2 } from "ai";
22162
23000
  function isExhaustedMessage(text) {
22163
23001
  return EXHAUSTED_PATTERNS.some((p) => p.test(text));
22164
23002
  }
22165
23003
  function classifyError(err) {
22166
23004
  const msg = err instanceof Error ? err.message : String(err);
22167
23005
  if (/spawn timeout/i.test(msg)) return "permanent";
22168
- if (err instanceof APICallError && err.isRetryable) {
23006
+ if (err instanceof APICallError2 && err.isRetryable) {
22169
23007
  return "transient";
22170
23008
  }
22171
23009
  for (const pattern of EXHAUSTED_PATTERNS) {
@@ -22577,7 +23415,7 @@ async function classifyIntentAsync(text, chatId) {
22577
23415
  return "agentic";
22578
23416
  }
22579
23417
  var intentCounts, CHAT_EXACT, MUTATION_PATTERNS, CHAT_QUESTION_PATTERNS, STRUCTURAL_PATTERNS, LLM_CLASSIFY_PROMPT, LLM_CLASSIFY_TIMEOUT_MS;
22580
- var init_classify = __esm({
23418
+ var init_classify2 = __esm({
22581
23419
  "src/intent/classify.ts"() {
22582
23420
  "use strict";
22583
23421
  init_store5();
@@ -23324,7 +24162,7 @@ function classifyAgentIntent(message) {
23324
24162
  return NOT_DETECTED;
23325
24163
  }
23326
24164
  var AGENT_SIGNAL_PATTERNS, CLAW_SIGNAL_PATTERNS, NEGATIVE_PATTERNS;
23327
- var init_classify2 = __esm({
24165
+ var init_classify3 = __esm({
23328
24166
  "src/agents/classify.ts"() {
23329
24167
  "use strict";
23330
24168
  AGENT_SIGNAL_PATTERNS = [
@@ -24546,6 +25384,9 @@ async function handleVoice(msg, channel) {
24546
25384
  await channel.sendText(chatId, "Couldn't transcribe the voice message. Make sure a transcription provider is configured via /voice.", { parseMode: "plain" });
24547
25385
  return;
24548
25386
  }
25387
+ if (getSttEcho(chatId)) {
25388
+ await channel.sendText(chatId, `\u{1F399} ${transcript}`, { parseMode: "plain" });
25389
+ }
24549
25390
  const vBackendId = getBackend(chatId) ?? "claude";
24550
25391
  const vLimitMsg = checkBackendLimits(vBackendId);
24551
25392
  if (vLimitMsg) {
@@ -24829,168 +25670,6 @@ var init_media = __esm({
24829
25670
  }
24830
25671
  });
24831
25672
 
24832
- // src/router/state.ts
24833
- var state_exports = {};
24834
- __export(state_exports, {
24835
- activeSideQuests: () => activeSideQuests,
24836
- bypassBusyCheck: () => bypassBusyCheck,
24837
- clearHistoryFilter: () => clearHistoryFilter,
24838
- clearPendingCliAddition: () => clearPendingCliAddition,
24839
- clearPendingModelResults: () => clearPendingModelResults,
24840
- clearPendingModelSearch: () => clearPendingModelSearch,
24841
- councilResults: () => councilResults,
24842
- dashboardClawWarnings: () => dashboardClawWarnings,
24843
- getActiveSideQuestCount: () => getActiveSideQuestCount,
24844
- historyFilters: () => historyFilters,
24845
- parseSideQuestPrefix: () => parseSideQuestPrefix,
24846
- pendingCliAdditions: () => pendingCliAdditions,
24847
- pendingInterrupts: () => pendingInterrupts,
24848
- pendingMcpImports: () => pendingMcpImports,
24849
- pendingModelResults: () => pendingModelResults,
24850
- pendingModelSearch: () => pendingModelSearch,
24851
- pendingNewchatUndo: () => pendingNewchatUndo,
24852
- pendingSummaryUndo: () => pendingSummaryUndo,
24853
- setCouncilResult: () => setCouncilResult,
24854
- setHistoryFilter: () => setHistoryFilter,
24855
- setPendingCliAddition: () => setPendingCliAddition,
24856
- setPendingModelResults: () => setPendingModelResults,
24857
- setPendingModelSearch: () => setPendingModelSearch,
24858
- startStateSweep: () => startStateSweep,
24859
- stopAllSideQuests: () => stopAllSideQuests,
24860
- stopStateSweep: () => stopStateSweep
24861
- });
24862
- function setHistoryFilter(chatId, filter) {
24863
- historyFilters.set(chatId, filter);
24864
- historyFilterTimestamps.set(chatId, Date.now());
24865
- }
24866
- function clearHistoryFilter(chatId) {
24867
- historyFilters.delete(chatId);
24868
- historyFilterTimestamps.delete(chatId);
24869
- }
24870
- function setCouncilResult(chatId, result) {
24871
- councilResults.set(chatId, result);
24872
- councilResultTimestamps.set(chatId, Date.now());
24873
- }
24874
- function setPendingModelSearch(chatId, state) {
24875
- pendingModelSearch.set(chatId, state);
24876
- pendingModelSearchTimestamps.set(chatId, Date.now());
24877
- }
24878
- function clearPendingModelSearch(chatId) {
24879
- pendingModelSearch.delete(chatId);
24880
- pendingModelSearchTimestamps.delete(chatId);
24881
- }
24882
- function setPendingModelResults(chatId, results) {
24883
- pendingModelResults.set(chatId, results);
24884
- }
24885
- function clearPendingModelResults(chatId) {
24886
- pendingModelResults.delete(chatId);
24887
- }
24888
- function parseSideQuestPrefix(text) {
24889
- const match = text.match(/^(?:sq|btw):\s*/i);
24890
- if (match) return { isSideQuest: true, cleanText: text.slice(match[0].length) };
24891
- return { isSideQuest: false, cleanText: text };
24892
- }
24893
- function getActiveSideQuestCount(chatId) {
24894
- return activeSideQuests.get(chatId)?.size ?? 0;
24895
- }
24896
- function stopAllSideQuests(chatId) {
24897
- const active = activeSideQuests.get(chatId);
24898
- if (active) {
24899
- for (const sqId of active) {
24900
- stopAgent(sqId);
24901
- }
24902
- }
24903
- }
24904
- function startStateSweep() {
24905
- if (sweepTimer) return;
24906
- sweepTimer = setInterval(() => {
24907
- const now = Date.now();
24908
- for (const [chatId, ts2] of dashboardClawWarnings) {
24909
- if (now - ts2 > STALE_THRESHOLD_MS) dashboardClawWarnings.delete(chatId);
24910
- }
24911
- for (const [cid, ts2] of historyFilterTimestamps) {
24912
- if (now - ts2 > STALE_THRESHOLD_MS) {
24913
- historyFilters.delete(cid);
24914
- historyFilterTimestamps.delete(cid);
24915
- }
24916
- }
24917
- for (const [cid, ts2] of councilResultTimestamps) {
24918
- if (now - ts2 > STALE_THRESHOLD_MS) {
24919
- councilResults.delete(cid);
24920
- councilResultTimestamps.delete(cid);
24921
- }
24922
- }
24923
- for (const [cid, ts2] of pendingModelSearchTimestamps) {
24924
- if (now - ts2 > STALE_THRESHOLD_MS) {
24925
- pendingModelSearch.delete(cid);
24926
- pendingModelSearchTimestamps.delete(cid);
24927
- }
24928
- }
24929
- for (const chatId of pendingInterrupts.keys()) {
24930
- if (!_interruptSeen.has(chatId)) {
24931
- _interruptSeen.add(chatId);
24932
- } else {
24933
- pendingInterrupts.delete(chatId);
24934
- _interruptSeen.delete(chatId);
24935
- }
24936
- }
24937
- for (const chatId of _interruptSeen) {
24938
- if (!pendingInterrupts.has(chatId)) _interruptSeen.delete(chatId);
24939
- }
24940
- for (const [cid, ts2] of pendingCliTimestamps) {
24941
- if (now - ts2 > STALE_THRESHOLD_MS) {
24942
- pendingCliAdditions.delete(cid);
24943
- pendingCliTimestamps.delete(cid);
24944
- }
24945
- }
24946
- for (const [cid, state] of pendingMcpImports) {
24947
- if (now - state.startedAt > STALE_THRESHOLD_MS) pendingMcpImports.delete(cid);
24948
- }
24949
- }, SWEEP_INTERVAL_MS);
24950
- sweepTimer.unref();
24951
- }
24952
- function stopStateSweep() {
24953
- if (sweepTimer) {
24954
- clearInterval(sweepTimer);
24955
- sweepTimer = null;
24956
- }
24957
- }
24958
- function setPendingCliAddition(chatId, messageId) {
24959
- pendingCliAdditions.set(chatId, messageId);
24960
- pendingCliTimestamps.set(chatId, Date.now());
24961
- }
24962
- function clearPendingCliAddition(chatId) {
24963
- pendingCliAdditions.delete(chatId);
24964
- pendingCliTimestamps.delete(chatId);
24965
- }
24966
- var pendingInterrupts, bypassBusyCheck, activeSideQuests, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, historyFilters, historyFilterTimestamps, councilResults, councilResultTimestamps, pendingModelSearch, pendingModelSearchTimestamps, pendingModelResults, SWEEP_INTERVAL_MS, STALE_THRESHOLD_MS, sweepTimer, _interruptSeen, pendingMcpImports, pendingCliAdditions, pendingCliTimestamps;
24967
- var init_state = __esm({
24968
- "src/router/state.ts"() {
24969
- "use strict";
24970
- init_agent();
24971
- pendingInterrupts = /* @__PURE__ */ new Map();
24972
- bypassBusyCheck = /* @__PURE__ */ new Set();
24973
- activeSideQuests = /* @__PURE__ */ new Map();
24974
- dashboardClawWarnings = /* @__PURE__ */ new Map();
24975
- pendingSummaryUndo = /* @__PURE__ */ new Map();
24976
- pendingNewchatUndo = /* @__PURE__ */ new Map();
24977
- historyFilters = /* @__PURE__ */ new Map();
24978
- historyFilterTimestamps = /* @__PURE__ */ new Map();
24979
- councilResults = /* @__PURE__ */ new Map();
24980
- councilResultTimestamps = /* @__PURE__ */ new Map();
24981
- pendingModelSearch = /* @__PURE__ */ new Map();
24982
- pendingModelSearchTimestamps = /* @__PURE__ */ new Map();
24983
- pendingModelResults = /* @__PURE__ */ new Map();
24984
- SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
24985
- STALE_THRESHOLD_MS = 30 * 60 * 1e3;
24986
- sweepTimer = null;
24987
- _interruptSeen = /* @__PURE__ */ new Set();
24988
- pendingMcpImports = /* @__PURE__ */ new Map();
24989
- pendingCliAdditions = /* @__PURE__ */ new Map();
24990
- pendingCliTimestamps = /* @__PURE__ */ new Map();
24991
- }
24992
- });
24993
-
24994
25673
  // src/router/sidequest.ts
24995
25674
  import { randomUUID as randomUUID3 } from "crypto";
24996
25675
  async function handleSideQuest(parentChatId, msg, channel) {
@@ -27954,6 +28633,7 @@ async function handleStopCommand(chatId, commandArgs, msg, channel) {
27954
28633
  const stopped = stopAgent(chatId);
27955
28634
  stopAllSideQuests(chatId);
27956
28635
  cancelAllAgents(chatId);
28636
+ if (stopped) addForceStoppedChat(chatId);
27957
28637
  await channel.sendText(
27958
28638
  chatId,
27959
28639
  stopped ? "Stopping current task..." : "Nothing is running.",
@@ -27984,7 +28664,7 @@ async function handleRememberCommand(chatId, commandArgs, msg, channel) {
27984
28664
  }
27985
28665
  const content = commandArgs.replace(/^that\s+/i, "");
27986
28666
  const trigger = content.split(/\s+/).slice(0, 3).join(" ");
27987
- saveMemoryWithEmbedding(trigger, content, "semantic");
28667
+ await remember(trigger, content);
27988
28668
  await channel.sendText(chatId, "Got it, I'll remember that.", { parseMode: "plain" });
27989
28669
  if (typeof channel.sendKeyboard === "function") {
27990
28670
  await channel.sendKeyboard(chatId, "", [
@@ -28069,11 +28749,15 @@ async function handleStopallCommand(chatId, commandArgs, msg, channel) {
28069
28749
  }
28070
28750
  async function handleEditjobCommand(chatId, commandArgs, msg, channel) {
28071
28751
  if (!commandArgs) {
28072
- await channel.sendText(chatId, "Usage: /editjob <job-id>", { parseMode: "plain" });
28752
+ await sendJobsBoard(chatId, channel, 1);
28073
28753
  return;
28074
28754
  }
28075
28755
  const editId = parseInt(commandArgs, 10);
28076
- await startEditWizard(chatId, editId, channel);
28756
+ if (isNaN(editId)) {
28757
+ await sendJobsBoard(chatId, channel, 1);
28758
+ return;
28759
+ }
28760
+ await sendJobDetail(chatId, editId, channel);
28077
28761
  }
28078
28762
  async function handleJobsCommand(chatId, commandArgs, msg, channel) {
28079
28763
  await sendJobsBoard(chatId, channel, 1);
@@ -28629,7 +29313,7 @@ async function handleMemoryCommand(chatId, commandArgs, msg, channel) {
28629
29313
  return;
28630
29314
  }
28631
29315
  deleteMemoryById(editId);
28632
- saveMemoryWithEmbedding(mem.trigger, newContent, mem.type);
29316
+ await remember(mem.trigger, newContent, { category: mem.category });
28633
29317
  await channel.sendText(chatId, `Memory #${editId} updated.`, { parseMode: "plain" });
28634
29318
  return;
28635
29319
  }
@@ -28814,9 +29498,9 @@ Recent directories:` : "Recent directories:";
28814
29498
  const buttons = recents.map((r) => [{ label: r.alias, data: `cwdpick:${r.alias}` }]);
28815
29499
  await channel.sendKeyboard(chatId, text, buttons);
28816
29500
  } else {
28817
- const list = recents.map((r) => ` ${r.alias} \u2192 ${r.path}`).join("\n");
29501
+ const list2 = recents.map((r) => ` ${r.alias} \u2192 ${r.path}`).join("\n");
28818
29502
  await channel.sendText(chatId, `${text}
28819
- ${list}`, { parseMode: "plain" });
29503
+ ${list2}`, { parseMode: "plain" });
28820
29504
  }
28821
29505
  return;
28822
29506
  }
@@ -28971,12 +29655,12 @@ async function handleGeminiAccountsCommand(chatId, commandArgs, msg, channel) {
28971
29655
  await channel.sendKeyboard(chatId, "Gemini Accounts & Rotation:", rows);
28972
29656
  } else {
28973
29657
  const currentMode = getGeminiRotationMode();
28974
- const list = slots.filter((s) => s.enabled).map((s) => {
29658
+ const list2 = slots.filter((s) => s.enabled).map((s) => {
28975
29659
  const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
28976
29660
  return `${icon} ${s.label || `slot-${s.id}`} (#${s.id})`;
28977
29661
  }).join("\n");
28978
29662
  await channel.sendText(chatId, `Slots:
28979
- ${list}
29663
+ ${list2}
28980
29664
 
28981
29665
  Rotation mode: ${currentMode}
28982
29666
  Use: /gemini_accounts <name> to pin`, { parseMode: "plain" });
@@ -29003,12 +29687,12 @@ Add with: <code>cc-claw ${slotBackend} add-key</code>`, { parseMode: "html" });
29003
29687
  await channel.sendKeyboard(chatId, `${slotDisplayName} Accounts & Rotation:`, rows);
29004
29688
  } else {
29005
29689
  const currentMode = getBackendRotationMode(slotBackend);
29006
- const list = slots.filter((s) => s.enabled).map((s) => {
29690
+ const list2 = slots.filter((s) => s.enabled).map((s) => {
29007
29691
  const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
29008
29692
  return `${icon} ${s.label || `slot-${s.id}`} (#${s.id})`;
29009
29693
  }).join("\n");
29010
29694
  await channel.sendText(chatId, `${slotDisplayName} Slots:
29011
- ${list}
29695
+ ${list2}
29012
29696
 
29013
29697
  Rotation mode: ${currentMode}
29014
29698
  Use: /${command} <name> to pin`, { parseMode: "plain" });
@@ -29339,10 +30023,10 @@ async function handleTasksCommand(chatId, commandArgs, msg, channel) {
29339
30023
  { label: "Abandoned", emoji: "\u{1F6AB}", status: "abandoned" }
29340
30024
  ];
29341
30025
  for (const { label: label2, emoji, status } of sections) {
29342
- const list = byStatus[status];
29343
- if (list.length === 0) continue;
30026
+ const list2 = byStatus[status];
30027
+ if (list2.length === 0) continue;
29344
30028
  lines.push(`${emoji} ${label2}:`);
29345
- for (const t of list) {
30029
+ for (const t of list2) {
29346
30030
  const assignee = t.assignee ? ` (\u2192 ${t.assignee.slice(0, 8)})` : "";
29347
30031
  lines.push(` #${t.id}: ${t.subject}${assignee}`);
29348
30032
  }
@@ -29525,11 +30209,12 @@ var init_command_handlers = __esm({
29525
30209
  init_image_gen();
29526
30210
  init_stt();
29527
30211
  init_agent();
29528
- init_classify();
30212
+ init_classify2();
29529
30213
  init_install();
29530
30214
  init_profile();
29531
30215
  init_heartbeat2();
29532
30216
  init_discover();
30217
+ init_engine();
29533
30218
  init_store5();
29534
30219
  init_summarize();
29535
30220
  init_session_log();
@@ -30662,6 +31347,9 @@ ${progressMsg}`,
30662
31347
  }
30663
31348
  setSttProvider(chatId, provider);
30664
31349
  await sendVoiceConfigKeyboard(chatId, channel, messageId);
31350
+ } else if (data === "vcfg:echo") {
31351
+ toggleSttEcho(chatId);
31352
+ await sendVoiceConfigKeyboard(chatId, channel, messageId);
30665
31353
  } else if (data.startsWith("vcfg:")) {
30666
31354
  const parts = data.slice(5).split(":");
30667
31355
  const action = parts[0];
@@ -30692,14 +31380,15 @@ ${progressMsg}`,
30692
31380
  await handleWizardCallback(chatId, data, channel);
30693
31381
  } else if (data.startsWith("job:")) {
30694
31382
  async function showJobAccountPicker(cid, jobId, backend2, model2, thinking2, ch) {
30695
- const isGemini = backend2 === "gemini";
30696
- const slots = isGemini ? getGeminiSlots() : getBackendSlots(backend2);
31383
+ const { getAdapter: getAdapter3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
31384
+ const adapter = getAdapter3(backend2);
31385
+ const isApiBackend = adapter.type === "api";
31386
+ const isGemini = backend2 === BACKEND.GEMINI;
31387
+ const slots = isApiBackend ? [] : isGemini ? getGeminiSlots() : getBackendSlots(backend2);
30697
31388
  const enabledSlots = slots.filter((s) => s.enabled);
30698
31389
  if (enabledSlots.length === 0 || typeof ch.sendKeyboard !== "function") {
30699
31390
  const { updateJob: updateJobFields } = await Promise.resolve().then(() => (init_store5(), store_exports5));
30700
31391
  updateJobFields(jobId, { backend: backend2, model: model2, thinking: thinking2, credentialSlotId: null });
30701
- const { getAdapter: getAdapter3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
30702
- const adapter = getAdapter3(backend2);
30703
31392
  await ch.sendText(cid, `Job #${jobId} updated: ${adapter.displayName} / ${model2} / ${thinking2}`, { parseMode: "plain" });
30704
31393
  await sendJobDetail(cid, jobId, ch);
30705
31394
  return;
@@ -31573,6 +32262,22 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
31573
32262
  } else if (rest.startsWith("opt")) {
31574
32263
  const { handleMemOptCallback: handleMemOptCallback2 } = await Promise.resolve().then(() => (init_optimize(), optimize_exports));
31575
32264
  await handleMemOptCallback2(chatId, rest, channel, messageId);
32265
+ } else if (rest === "sweep:dismiss") {
32266
+ if (messageId && channel.editText) {
32267
+ await channel.editText(chatId, messageId, "\u2705 Memory sweep dismissed.", "plain").catch(() => {
32268
+ });
32269
+ }
32270
+ } else if (rest === "sweep:toggle") {
32271
+ const { getMetaValue: getMetaValue2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
32272
+ const { SWEEP_ENABLED_KEY: SWEEP_ENABLED_KEY2, enableSweep: enableSweep2, disableSweep: disableSweep2 } = await Promise.resolve().then(() => (init_sweep(), sweep_exports));
32273
+ const currentlyEnabled = getMetaValue2(SWEEP_ENABLED_KEY2) === "1";
32274
+ if (currentlyEnabled) {
32275
+ disableSweep2();
32276
+ } else {
32277
+ enableSweep2(chatId);
32278
+ }
32279
+ const { sendMemoryPage: sendMemoryPage3 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
32280
+ await sendMemoryPage3(chatId, channel, 1, messageId);
31576
32281
  } else if (rest === "noop") {
31577
32282
  }
31578
32283
  return;
@@ -33169,7 +33874,8 @@ Try a different keyword.`,
33169
33874
  if (rememberMatch) {
33170
33875
  const content = rememberMatch[1];
33171
33876
  const trigger = content.split(/\s+/).slice(0, 3).join(" ");
33172
- saveMemoryWithEmbedding(trigger, content, "semantic");
33877
+ remember(trigger, content).catch(() => {
33878
+ });
33173
33879
  await channel.sendText(chatId, "Got it, I'll remember that.", { parseMode: "plain" });
33174
33880
  return;
33175
33881
  }
@@ -33748,12 +34454,13 @@ var init_router2 = __esm({
33748
34454
  init_agent();
33749
34455
  init_retry();
33750
34456
  init_quota();
33751
- init_classify();
34457
+ init_classify2();
34458
+ init_engine();
33752
34459
  init_store5();
33753
34460
  init_backends();
33754
34461
  init_wizard();
33755
34462
  init_ollama3();
33756
- init_classify2();
34463
+ init_classify3();
33757
34464
  init_session_log2();
33758
34465
  init_live_status();
33759
34466
  init_detect();
@@ -34021,6 +34728,28 @@ async function executeJob(job) {
34021
34728
  async function runWithRetry(job, model2, runId, t0) {
34022
34729
  let lastError;
34023
34730
  const chatId = job.sessionType === "isolated" ? `cron:${job.id}:${runId}` : job.chatId;
34731
+ if (job.jobType === "memory_sweep") {
34732
+ const { runWeeklySweep: runWeeklySweep2 } = await Promise.resolve().then(() => (init_sweep(), sweep_exports));
34733
+ const { getChannelRegistry: getChannelRegistry2 } = await Promise.resolve().then(() => (init_delivery(), delivery_exports));
34734
+ const channelName = job.channel ?? "telegram";
34735
+ const channel = getChannelRegistry2()?.get(channelName);
34736
+ const deliveryTarget = job.target ?? job.chatId;
34737
+ if (!channel) {
34738
+ warn(`[sweep] Job #${job.id}: channel "${channelName}" not found \u2014 sweep ran silently`);
34739
+ }
34740
+ const result = await runWeeklySweep2(
34741
+ deliveryTarget,
34742
+ channel,
34743
+ resolveJobBackendId(job) ?? void 0,
34744
+ job.model ?? void 0
34745
+ );
34746
+ const parts = [];
34747
+ if (result.cleanedUp > 0) parts.push(`cleaned up ${result.cleanedUp} expired memories`);
34748
+ if (result.suggestionsCount > 0) parts.push(`found ${result.suggestionsCount} suggestions`);
34749
+ if (result.error) parts.push(`error: ${result.error}`);
34750
+ const summary = parts.length > 0 ? parts.join(", ") : "memory bank healthy, no action needed";
34751
+ return { text: summary };
34752
+ }
34024
34753
  if (job.jobType === "reflection") {
34025
34754
  const { runNightlyReflection: runNightlyReflection2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
34026
34755
  const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
@@ -35503,7 +36232,7 @@ var init_telegram2 = __esm({
35503
36232
  }
35504
36233
  return keyboard;
35505
36234
  }
35506
- async editKeyboard(chatId, messageId, text, buttons) {
36235
+ async editKeyboard(chatId, messageId, text, buttons, opts) {
35507
36236
  const keyboard = this.buildInlineKeyboard(buttons);
35508
36237
  const formatted = sanitizeForTelegram(formatForTelegram(text));
35509
36238
  try {
@@ -35513,7 +36242,8 @@ var init_telegram2 = __esm({
35513
36242
  () => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
35514
36243
  parse_mode: "HTML",
35515
36244
  reply_markup: keyboard
35516
- })
36245
+ }),
36246
+ opts?.priority
35517
36247
  );
35518
36248
  return true;
35519
36249
  } catch (err) {
@@ -35527,7 +36257,8 @@ var init_telegram2 = __esm({
35527
36257
  parseInt(messageId),
35528
36258
  formatted.replace(/<[^>]+>/g, ""),
35529
36259
  { reply_markup: keyboard }
35530
- )
36260
+ ),
36261
+ opts?.priority
35531
36262
  );
35532
36263
  return true;
35533
36264
  } catch (err2) {
@@ -36606,7 +37337,7 @@ async function main() {
36606
37337
  pruneMessageLog(30, 2e3);
36607
37338
  bootstrapBuiltinMcps(getDb());
36608
37339
  try {
36609
- const { resetIntentStats: resetIntentStats2 } = await Promise.resolve().then(() => (init_classify(), classify_exports));
37340
+ const { resetIntentStats: resetIntentStats2 } = await Promise.resolve().then(() => (init_classify2(), classify_exports));
36610
37341
  resetIntentStats2();
36611
37342
  } catch {
36612
37343
  }
@@ -36782,6 +37513,22 @@ ${lines.join("\n")}`;
36782
37513
  migrateEmbeddings().catch((err) => error("[cc-claw] Embedding migration failed:", err));
36783
37514
  initHeartbeat(channelRegistry);
36784
37515
  startAllHeartbeats();
37516
+ try {
37517
+ const { getMetaValue: getMetaValue2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
37518
+ const { SWEEP_ENABLED_KEY: SWEEP_ENABLED_KEY2, findSweepJob: findSweepJob2, enableSweep: enableSweep2 } = await Promise.resolve().then(() => (init_sweep(), sweep_exports));
37519
+ const sweepEnabled = getMetaValue2(SWEEP_ENABLED_KEY2);
37520
+ if (sweepEnabled === "1") {
37521
+ if (!findSweepJob2()) {
37522
+ const primaryChatId = (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim();
37523
+ if (primaryChatId) {
37524
+ enableSweep2(primaryChatId);
37525
+ log("[sweep] Restored weekly memory sweep job from meta setting");
37526
+ }
37527
+ }
37528
+ }
37529
+ } catch (err) {
37530
+ warn(`[sweep] Failed to restore sweep job: ${err instanceof Error ? err.message : String(err)}`);
37531
+ }
36785
37532
  startHealthMonitor3(channelRegistry.list(), handleMessage);
36786
37533
  Promise.resolve().then(() => (init_health(), health_exports)).then(({ startHealthMonitor: startMcpHealthMonitor }) => {
36787
37534
  startMcpHealthMonitor(getDb());
@@ -36826,7 +37573,7 @@ ${lines.join("\n")}`;
36826
37573
  ;
36827
37574
  shutdownOrchestrator();
36828
37575
  shutdownScheduler();
36829
- flushMemorySalienceUpdates();
37576
+ flushMemoryHalfLifeUpdates();
36830
37577
  flushSummarySalienceUpdates();
36831
37578
  await Promise.race([
36832
37579
  summarizeAllPending(),
@@ -37779,6 +38526,7 @@ async function logsCommand(opts) {
37779
38526
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
37780
38527
  console.log(tailLines.join("\n"));
37781
38528
  if (opts.follow) {
38529
+ globalThis._cliStayAlive = true;
37782
38530
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
37783
38531
  let lastLength = content.length;
37784
38532
  watchFile2(logFile, { interval: 500 }, () => {
@@ -37869,6 +38617,7 @@ async function sessionLogsTail(opts) {
37869
38617
  console.log(line);
37870
38618
  }
37871
38619
  if (opts.follow) {
38620
+ globalThis._cliStayAlive = true;
37872
38621
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
37873
38622
  let lastLength = 0;
37874
38623
  try {
@@ -37976,9 +38725,9 @@ async function geminiList(globalOpts) {
37976
38725
  email: s.slot_type === "oauth" ? resolveOAuthEmail(s.config_home) : null
37977
38726
  }));
37978
38727
  output(enriched, (data) => {
37979
- const list = data;
38728
+ const list2 = data;
37980
38729
  const lines = ["", divider("Gemini Credential Slots"), ""];
37981
- for (const s of list) {
38730
+ for (const s of list2) {
37982
38731
  const now = (/* @__PURE__ */ new Date()).toISOString();
37983
38732
  const inCooldown = s.cooldown_until && s.cooldown_until > now;
37984
38733
  const icon = !s.enabled ? error2("\u25CB disabled") : inCooldown ? warning("\u25D1 cooldown") : success("\u25CF active");
@@ -38301,7 +39050,7 @@ async function resolveSlotId2(backend2, idOrLabel) {
38301
39050
  return match?.id ?? null;
38302
39051
  }
38303
39052
  function makeList(backend2, displayName) {
38304
- return async function list(_globalOpts) {
39053
+ return async function list2(_globalOpts) {
38305
39054
  requireDb2();
38306
39055
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
38307
39056
  const readDb = openDatabaseReadOnly2();
@@ -38316,9 +39065,9 @@ Add one with: cc-claw ${backend2} add-account or cc-claw ${backend2} add-key`)
38316
39065
  return;
38317
39066
  }
38318
39067
  output(slots, (data) => {
38319
- const list2 = data;
39068
+ const list3 = data;
38320
39069
  const lines = ["", divider(`${displayName} Credential Slots`), ""];
38321
- for (const s of list2) {
39070
+ for (const s of list3) {
38322
39071
  const now = (/* @__PURE__ */ new Date()).toISOString();
38323
39072
  const inCooldown = s.cooldown_until && s.cooldown_until > now;
38324
39073
  const icon = !s.enabled ? error2("\u25CB disabled") : inCooldown ? warning("\u25D1 cooldown") : success("\u25CF active");
@@ -38782,9 +39531,9 @@ async function ollamaList(globalOpts) {
38782
39531
  modelCount: modelCounts.get(s.id) ?? 0
38783
39532
  }));
38784
39533
  output(data, (d) => {
38785
- const list = d;
39534
+ const list2 = d;
38786
39535
  const lines = ["", divider("Ollama Servers"), ""];
38787
- for (const s of list) {
39536
+ for (const s of list2) {
38788
39537
  const dot = statusDot(s.status === "online" ? "active" : "offline");
38789
39538
  lines.push(` ${dot} ${s.name} ${muted(`(${s.host}:${s.port})`)}`);
38790
39539
  lines.push(` Models: ${s.modelCount} \xB7 Status: ${s.status === "online" ? success("online") : error2("offline")}`);
@@ -38936,13 +39685,13 @@ async function ollamaDiscover(globalOpts, opts) {
38936
39685
  contextWindow: m.contextWindow,
38937
39686
  sizeBytes: m.sizeBytes
38938
39687
  })), (d) => {
38939
- const list = d;
38940
- if (list.length === 0) {
39688
+ const list2 = d;
39689
+ if (list2.length === 0) {
38941
39690
  return "\n No models found. Check server connectivity.\n";
38942
39691
  }
38943
39692
  const lines = [`
38944
- ${success(`\u2713 Discovered ${list.length} model(s):`)}`, ""];
38945
- for (const m of list) {
39693
+ ${success(`\u2713 Discovered ${list2.length} model(s):`)}`, ""];
39694
+ for (const m of list2) {
38946
39695
  const sizeGB = m.sizeBytes > 0 ? `${(m.sizeBytes / 1e9).toFixed(1)}GB` : "";
38947
39696
  const ctxK = m.contextWindow ? `${(m.contextWindow / 1e3).toFixed(0)}K ctx` : "";
38948
39697
  const meta = [m.parameterSize, sizeGB, ctxK].filter(Boolean).join(" \xB7 ");
@@ -39029,9 +39778,9 @@ async function backendList(globalOpts) {
39029
39778
  defaultModel: a.defaultModel
39030
39779
  }));
39031
39780
  output(data, (d) => {
39032
- const list = d;
39781
+ const list2 = d;
39033
39782
  const lines = ["", divider("Backends"), ""];
39034
- for (const b of list) {
39783
+ for (const b of list2) {
39035
39784
  const marker = b.active ? success("\u25CF ") : " ";
39036
39785
  lines.push(` ${marker}${b.displayName} (${b.id})${b.active ? success(" \u2190 active") : ""}`);
39037
39786
  lines.push(` Default model: ${muted(b.defaultModel)}`);
@@ -39349,12 +40098,12 @@ async function cronList(globalOpts) {
39349
40098
  const jobs = readDb.prepare("SELECT * FROM jobs ORDER BY id").all();
39350
40099
  readDb.close();
39351
40100
  output(jobs, (d) => {
39352
- const list = d;
39353
- if (list.length === 0) return `
40101
+ const list2 = d;
40102
+ if (list2.length === 0) return `
39354
40103
  ${muted("No scheduled jobs.")}
39355
40104
  `;
39356
- const lines = ["", divider(`Scheduled Jobs (${list.length})`), ""];
39357
- for (const j of list) {
40105
+ const lines = ["", divider(`Scheduled Jobs (${list2.length})`), ""];
40106
+ for (const j of list2) {
39358
40107
  const status = !j.active ? "cancelled" : !j.enabled ? "paused" : "active";
39359
40108
  const schedule2 = j.cron ?? (j.at_time ? `at ${j.at_time}` : j.every_ms ? `every ${j.every_ms / 1e3}s` : "?");
39360
40109
  const tz = j.timezone !== "UTC" ? ` (${j.timezone})` : "";
@@ -39574,12 +40323,12 @@ async function cronRuns(globalOpts, jobId, opts) {
39574
40323
  const runs = readDb.prepare(query).all(...params);
39575
40324
  readDb.close();
39576
40325
  output(runs, (d) => {
39577
- const list = d;
39578
- if (list.length === 0) return `
40326
+ const list2 = d;
40327
+ if (list2.length === 0) return `
39579
40328
  ${muted(jobId ? `No runs for job #${jobId}.` : "No run history yet.")}
39580
40329
  `;
39581
40330
  const lines = ["", divider("Run History"), ""];
39582
- for (const r of list) {
40331
+ for (const r of list2) {
39583
40332
  const duration = r.duration_ms ? ` (${(r.duration_ms / 1e3).toFixed(1)}s)` : "";
39584
40333
  lines.push(` #${r.job_id} [${r.status}] ${formatLocalDateTime(r.started_at)}${duration}`);
39585
40334
  if (r.error) lines.push(` Error: ${r.error.slice(0, 100)}`);
@@ -39622,12 +40371,12 @@ async function agentsList(globalOpts) {
39622
40371
  ).all();
39623
40372
  readDb.close();
39624
40373
  output(agents2, (d) => {
39625
- const list = d;
39626
- if (list.length === 0) return `
40374
+ const list2 = d;
40375
+ if (list2.length === 0) return `
39627
40376
  ${muted("No active agents.")}
39628
40377
  `;
39629
- const lines = ["", divider(`Active Agents (${list.length})`), ""];
39630
- for (const a of list) {
40378
+ const lines = ["", divider(`Active Agents (${list2.length})`), ""];
40379
+ for (const a of list2) {
39631
40380
  const shortId = a.id?.slice(0, 8) ?? "?";
39632
40381
  lines.push(` ${statusDot(a.status)} ${shortId} (${a.runnerId}) \u2014 ${a.status}`);
39633
40382
  if (a.task) lines.push(` Task: ${a.task.slice(0, 80)}${a.task.length > 80 ? "\u2026" : ""}`);
@@ -39653,12 +40402,12 @@ async function tasksList(globalOpts) {
39653
40402
  ).all();
39654
40403
  readDb.close();
39655
40404
  output(tasks, (d) => {
39656
- const list = d;
39657
- if (list.length === 0) return `
40405
+ const list2 = d;
40406
+ if (list2.length === 0) return `
39658
40407
  ${muted("No active tasks.")}
39659
40408
  `;
39660
- const lines = ["", divider(`Task Board (${list.length})`), ""];
39661
- for (const t of list) {
40409
+ const lines = ["", divider(`Task Board (${list2.length})`), ""];
40410
+ for (const t of list2) {
39662
40411
  const assignee = t.assignee ? ` (\u2192 ${t.assignee.slice(0, 8)})` : "";
39663
40412
  lines.push(` ${statusDot(t.status === "completed" ? "ok" : t.status === "in_progress" ? "running" : "paused")} #${t.id}: ${t.subject}${assignee}`);
39664
40413
  }
@@ -39737,12 +40486,12 @@ async function runnersList(globalOpts) {
39737
40486
  displayName: r.displayName,
39738
40487
  specialties: r.capabilities.specialties ?? []
39739
40488
  })), (d) => {
39740
- const list = d;
39741
- if (list.length === 0) return `
40489
+ const list2 = d;
40490
+ if (list2.length === 0) return `
39742
40491
  ${muted("No runners registered.")}
39743
40492
  `;
39744
- const lines = ["", divider(`Registered Runners (${list.length})`), ""];
39745
- for (const r of list) {
40493
+ const lines = ["", divider(`Registered Runners (${list2.length})`), ""];
40494
+ for (const r of list2) {
39746
40495
  const specs = r.specialties.length > 0 ? ` \u2014 ${r.specialties.join(", ")}` : "";
39747
40496
  lines.push(` \u2022 ${r.id} (${r.displayName})${specs}`);
39748
40497
  }
@@ -39965,9 +40714,9 @@ async function usageTokens(globalOpts) {
39965
40714
  });
39966
40715
  readDb.close();
39967
40716
  output(data, (d) => {
39968
- const list = d;
40717
+ const list2 = d;
39969
40718
  const lines = ["", divider("Backend usage (last 24h)"), ""];
39970
- for (const u of list) {
40719
+ for (const u of list2) {
39971
40720
  lines.push(` ${u.displayName}: ${(u.input_tokens / 1e3).toFixed(1)}K in / ${(u.output_tokens / 1e3).toFixed(1)}K out (${u.request_count} requests)`);
39972
40721
  }
39973
40722
  lines.push("");
@@ -39981,13 +40730,13 @@ async function limitsList(globalOpts) {
39981
40730
  const limits2 = readDb.prepare("SELECT * FROM backend_limits").all();
39982
40731
  readDb.close();
39983
40732
  output(limits2, (d) => {
39984
- const list = d;
39985
- if (list.length === 0) return `
40733
+ const list2 = d;
40734
+ if (list2.length === 0) return `
39986
40735
  ${muted("No usage limits set.")}
39987
40736
  ${muted("Set with: cc-claw usage limits set <backend> <window> <tokens>")}
39988
40737
  `;
39989
40738
  const lines = ["", divider("Usage limits"), ""];
39990
- for (const l of list) {
40739
+ for (const l of list2) {
39991
40740
  lines.push(` ${l.backend} (${l.window}): ${l.max_input_tokens ? `${(l.max_input_tokens / 1e3).toFixed(0)}K input tokens` : "no limit"}`);
39992
40741
  }
39993
40742
  lines.push("");
@@ -40295,9 +41044,9 @@ async function toolsList(globalOpts) {
40295
41044
  const toolMap = new Map(rows.map((r) => [r.tool, !!r.enabled]));
40296
41045
  const tools2 = ALL_TOOLS4.map((t) => ({ name: t, enabled: toolMap.get(t) ?? true }));
40297
41046
  output(tools2, (d) => {
40298
- const list = d;
41047
+ const list2 = d;
40299
41048
  const lines = ["", divider("Tools"), ""];
40300
- for (const t of list) {
41049
+ for (const t of list2) {
40301
41050
  lines.push(` ${checkMark(t.enabled)} ${t.name}`);
40302
41051
  }
40303
41052
  lines.push("");
@@ -40824,13 +41573,13 @@ async function chatsList(_globalOpts) {
40824
41573
  const aliases = readDb.prepare("SELECT alias, chat_id FROM chat_aliases ORDER BY alias").all();
40825
41574
  readDb.close();
40826
41575
  output(aliases, (d) => {
40827
- const list = d;
40828
- if (list.length === 0) return `
41576
+ const list2 = d;
41577
+ if (list2.length === 0) return `
40829
41578
  ${muted("No chat aliases configured yet.")}
40830
41579
  ${muted("Use: cc-claw chats alias <chat_id> <name>")}
40831
41580
  `;
40832
41581
  const lines = ["", divider("Chat Aliases"), ""];
40833
- for (const a of list) {
41582
+ for (const a of list2) {
40834
41583
  lines.push(` ${a.alias} \u2192 ${a.chat_id}`);
40835
41584
  }
40836
41585
  lines.push("");
@@ -40893,13 +41642,13 @@ async function skillsList(_globalOpts) {
40893
41642
  sources: s.sources,
40894
41643
  filePath: s.filePath
40895
41644
  })), (d) => {
40896
- const list = d;
40897
- if (list.length === 0) return `
41645
+ const list2 = d;
41646
+ if (list2.length === 0) return `
40898
41647
  ${muted("No skills found.")}
40899
41648
  ${muted("Install with: cc-claw skills install <github-url>")}
40900
41649
  `;
40901
- const lines = ["", divider(`Skills (${list.length})`), ""];
40902
- for (const s of list) {
41650
+ const lines = ["", divider(`Skills (${list2.length})`), ""];
41651
+ for (const s of list2) {
40903
41652
  const tags = s.sources.join(", ");
40904
41653
  const desc = s.description ? ` \u2014 ${s.description.slice(0, 60)}` : "";
40905
41654
  lines.push(` \u2022 ${s.name} [${muted(tags)}]${desc}`);
@@ -40964,14 +41713,14 @@ async function mcpList(globalOpts) {
40964
41713
  const mcps = listMcpServers2(db3);
40965
41714
  db3.close();
40966
41715
  output(mcps, (d) => {
40967
- const list = d;
40968
- if (list.length === 0) {
41716
+ const list2 = d;
41717
+ if (list2.length === 0) {
40969
41718
  return `
40970
41719
  ${muted("No MCP servers registered. Use `cc-claw mcp add` or `cc-claw mcp import`.")}
40971
41720
  `;
40972
41721
  }
40973
- const lines = ["", divider(`MCP Servers (${list.length})`), ""];
40974
- for (const m of list) {
41722
+ const lines = ["", divider(`MCP Servers (${list2.length})`), ""];
41723
+ for (const m of list2) {
40975
41724
  const lock = SYSTEM_MCP_NAMES.has(m.name) ? " \u{1F512}" : "";
40976
41725
  const pin = m.enabledByDefault ? " \u{1F4CC}" : "";
40977
41726
  const desc = m.description ? ` \u2014 ${muted(m.description)}` : "";
@@ -41378,6 +42127,7 @@ async function tuiCommand(globalOpts, cmdOpts) {
41378
42127
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
41379
42128
  process.exit(1);
41380
42129
  }
42130
+ globalThis._cliStayAlive = true;
41381
42131
  const chatId = resolveChatId2(globalOpts);
41382
42132
  let theme = getTheme();
41383
42133
  const rl2 = createInterface10({
@@ -43387,6 +44137,7 @@ program.command("council").alias("debate").description("Multi-model council deba
43387
44137
  console.log("Select 2+ models, pose a question, and they debate anonymously for up to 3 rounds.");
43388
44138
  });
43389
44139
  program.command("start", { hidden: true }).description("Run the bot in the foreground (use 'service start' for background daemon)").action(async () => {
44140
+ globalThis._cliStayAlive = true;
43390
44141
  await Promise.resolve().then(() => (init_index(), index_exports));
43391
44142
  });
43392
44143
  program.command("install", { hidden: true }).description("Install as background service \u2014 alias for service install").action(async () => {
@@ -43398,6 +44149,7 @@ program.command("uninstall", { hidden: true }).description("Remove background se
43398
44149
  uninstallService2();
43399
44150
  });
43400
44151
  program.command("setup").description("Interactive configuration wizard").option("--dry-run", "Run the wizard without saving anything (demo/test mode)").action(async (opts) => {
44152
+ globalThis._cliStayAlive = true;
43401
44153
  if (opts.dryRun) process.env.CC_CLAW_SETUP_DRY_RUN = "1";
43402
44154
  await Promise.resolve().then(() => (init_setup(), setup_exports));
43403
44155
  });
@@ -43433,10 +44185,12 @@ Update available: v${latest} (current: v${VERSION})`);
43433
44185
  return;
43434
44186
  }
43435
44187
  await program.parseAsync(argv);
43436
- if (process.stdout.writableLength > 0) {
43437
- process.stdout.once("drain", () => process.exit(0));
43438
- } else {
43439
- setImmediate(() => process.exit(0));
44188
+ if (!globalThis._cliStayAlive) {
44189
+ if (process.stdout.writableLength > 0) {
44190
+ process.stdout.once("drain", () => process.exit(0));
44191
+ } else {
44192
+ setTimeout(() => process.exit(0), 50);
44193
+ }
43440
44194
  }
43441
44195
  }
43442
44196