cc-claw 0.27.2 → 0.29.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/agents/mcp-server.js +3 -1
- package/dist/cli.js +1968 -782
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ var VERSION;
|
|
|
33
33
|
var init_version = __esm({
|
|
34
34
|
"src/version.ts"() {
|
|
35
35
|
"use strict";
|
|
36
|
-
VERSION = true ? "0.
|
|
36
|
+
VERSION = true ? "0.29.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
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
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
|
|
2322
|
-
if (
|
|
2323
|
-
|
|
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
|
|
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
|
|
2333
|
-
stmt.run(
|
|
2434
|
+
for (const [id] of pendingHalfLifeExtensions) {
|
|
2435
|
+
stmt.run(id);
|
|
2334
2436
|
}
|
|
2335
2437
|
});
|
|
2336
2438
|
txn();
|
|
2337
2439
|
} catch {
|
|
2338
2440
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
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()
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2472
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
6464
|
-
const entry =
|
|
6817
|
+
const list2 = whitelist ?? getApiCliWhitelist(chatId);
|
|
6818
|
+
const entry = list2.find((w) => w.cli === firstWord);
|
|
6465
6819
|
if (!entry) {
|
|
6466
|
-
const allowed =
|
|
6820
|
+
const allowed = list2.map((w) => w.cli).join(", ") || "(none)";
|
|
6467
6821
|
return {
|
|
6468
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
7078
|
+
RESTRICTED_BASH_TIMEOUT_MS = 3e5;
|
|
6714
7079
|
MAX_READ_BYTES = 1e5;
|
|
6715
7080
|
lastDdgSearchAt = 0;
|
|
6716
7081
|
DDG_MIN_GAP_MS = 3e3;
|
|
@@ -7194,6 +7559,380 @@ var init_html = __esm({
|
|
|
7194
7559
|
}
|
|
7195
7560
|
});
|
|
7196
7561
|
|
|
7562
|
+
// src/channels/telegram-throttle.ts
|
|
7563
|
+
var telegram_throttle_exports = {};
|
|
7564
|
+
__export(telegram_throttle_exports, {
|
|
7565
|
+
CircuitState: () => CircuitState,
|
|
7566
|
+
Priority: () => Priority,
|
|
7567
|
+
TelegramThrottle: () => TelegramThrottle,
|
|
7568
|
+
getThrottleState: () => getThrottleState
|
|
7569
|
+
});
|
|
7570
|
+
import { GrammyError } from "grammy";
|
|
7571
|
+
function perChatInterval(chatId) {
|
|
7572
|
+
return parseInt(chatId) < 0 ? PER_GROUP_INTERVAL_MS : PER_DM_INTERVAL_MS;
|
|
7573
|
+
}
|
|
7574
|
+
function getThrottleState() {
|
|
7575
|
+
if (!_activeThrottle) return null;
|
|
7576
|
+
return _activeThrottle.getState();
|
|
7577
|
+
}
|
|
7578
|
+
function is429(err) {
|
|
7579
|
+
return err instanceof GrammyError && err.error_code === 429;
|
|
7580
|
+
}
|
|
7581
|
+
function sleep(ms) {
|
|
7582
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
7583
|
+
}
|
|
7584
|
+
var PER_DM_INTERVAL_MS, PER_GROUP_INTERVAL_MS, GLOBAL_INTERVAL_MS, MAX_RETRIES2, RETRY_DELAY_MS, MAX_QUEUE_SIZE, EDIT_PRESSURE_THRESHOLD, MAX_PER_CHAT_QUEUE, MAX_TOTAL_PAUSE_MS, CIRCUIT_TRIP_THRESHOLD, CIRCUIT_TRIP_WINDOW_MS, CIRCUIT_COOLDOWN_STEP_SEC, CIRCUIT_RESET_WINDOW_MS, CircuitState, Priority, _activeThrottle, TelegramThrottle;
|
|
7585
|
+
var init_telegram_throttle = __esm({
|
|
7586
|
+
"src/channels/telegram-throttle.ts"() {
|
|
7587
|
+
"use strict";
|
|
7588
|
+
init_log();
|
|
7589
|
+
PER_DM_INTERVAL_MS = 1e3;
|
|
7590
|
+
PER_GROUP_INTERVAL_MS = 3500;
|
|
7591
|
+
GLOBAL_INTERVAL_MS = 100;
|
|
7592
|
+
MAX_RETRIES2 = 2;
|
|
7593
|
+
RETRY_DELAY_MS = 1e3;
|
|
7594
|
+
MAX_QUEUE_SIZE = 60;
|
|
7595
|
+
EDIT_PRESSURE_THRESHOLD = MAX_QUEUE_SIZE / 2;
|
|
7596
|
+
MAX_PER_CHAT_QUEUE = 15;
|
|
7597
|
+
MAX_TOTAL_PAUSE_MS = 5 * 60 * 1e3;
|
|
7598
|
+
CIRCUIT_TRIP_THRESHOLD = 3;
|
|
7599
|
+
CIRCUIT_TRIP_WINDOW_MS = 5 * 60 * 1e3;
|
|
7600
|
+
CIRCUIT_COOLDOWN_STEP_SEC = 5;
|
|
7601
|
+
CIRCUIT_RESET_WINDOW_MS = 5 * 60 * 1e3;
|
|
7602
|
+
CircuitState = /* @__PURE__ */ ((CircuitState2) => {
|
|
7603
|
+
CircuitState2["CLOSED"] = "closed";
|
|
7604
|
+
CircuitState2["OPEN"] = "open";
|
|
7605
|
+
CircuitState2["HALF_OPEN"] = "half_open";
|
|
7606
|
+
return CircuitState2;
|
|
7607
|
+
})(CircuitState || {});
|
|
7608
|
+
Priority = /* @__PURE__ */ ((Priority2) => {
|
|
7609
|
+
Priority2[Priority2["P0_CRITICAL"] = 0] = "P0_CRITICAL";
|
|
7610
|
+
Priority2[Priority2["P1_NORMAL"] = 1] = "P1_NORMAL";
|
|
7611
|
+
Priority2[Priority2["P2_COSMETIC"] = 2] = "P2_COSMETIC";
|
|
7612
|
+
return Priority2;
|
|
7613
|
+
})(Priority || {});
|
|
7614
|
+
_activeThrottle = null;
|
|
7615
|
+
TelegramThrottle = class {
|
|
7616
|
+
queue = [];
|
|
7617
|
+
processing = false;
|
|
7618
|
+
lastSendPerChat = /* @__PURE__ */ new Map();
|
|
7619
|
+
perChatQueueCount = /* @__PURE__ */ new Map();
|
|
7620
|
+
// O(1) per-chat depth lookup
|
|
7621
|
+
lastGlobalSend = 0;
|
|
7622
|
+
// Pause state
|
|
7623
|
+
pausedUntil = 0;
|
|
7624
|
+
pauseStartedAt = 0;
|
|
7625
|
+
// Per-chat cosmetic backoff — tryBestEffort() 429s only affect future
|
|
7626
|
+
// best-effort calls for the SAME chat, never triggering a global pause.
|
|
7627
|
+
cosmeticPausedUntil = /* @__PURE__ */ new Map();
|
|
7628
|
+
// Circuit breaker state — tracks repeated 429s and manages recovery
|
|
7629
|
+
circuitState = "closed" /* CLOSED */;
|
|
7630
|
+
circuitTrips = [];
|
|
7631
|
+
// timestamps of recent 429s
|
|
7632
|
+
circuitCooldownUntil = 0;
|
|
7633
|
+
// when OPEN cooldown expires
|
|
7634
|
+
lastSuccessfulSend = 0;
|
|
7635
|
+
// for resetting trip count after 5min of success
|
|
7636
|
+
constructor() {
|
|
7637
|
+
_activeThrottle = this;
|
|
7638
|
+
}
|
|
7639
|
+
/** Enqueue a Telegram API call with automatic pacing and 429 handling.
|
|
7640
|
+
* Priority controls queue insertion order:
|
|
7641
|
+
* P0_CRITICAL — keyboard responses, finalize edits, /stop — always first
|
|
7642
|
+
* P1_NORMAL — agent responses, cron deliveries — default
|
|
7643
|
+
* P2_COSMETIC — live-status streaming edits — dropped first on overflow
|
|
7644
|
+
*
|
|
7645
|
+
* Accepts `Priority | boolean` for backward compatibility during migration.
|
|
7646
|
+
* `true` maps to P0_CRITICAL, `false`/`undefined` maps to P1_NORMAL. */
|
|
7647
|
+
async send(chatId, label2, fn, priority) {
|
|
7648
|
+
const prio = priority === true ? 0 /* P0_CRITICAL */ : typeof priority === "number" ? priority : 1 /* P1_NORMAL */;
|
|
7649
|
+
if (prio === 2 /* P2_COSMETIC */) {
|
|
7650
|
+
if (this.isPaused()) {
|
|
7651
|
+
throw new Error("Throttle paused (rate limit active) \u2014 edit skipped");
|
|
7652
|
+
}
|
|
7653
|
+
if (this.queue.length >= EDIT_PRESSURE_THRESHOLD) {
|
|
7654
|
+
throw new Error("Throttle queue pressured \u2014 edit skipped");
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
return new Promise((resolve3, reject) => {
|
|
7658
|
+
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
7659
|
+
let dropIdx = -1;
|
|
7660
|
+
for (let i = this.queue.length - 1; i >= 0; i--) {
|
|
7661
|
+
if (this.queue[i].priority === 2 /* P2_COSMETIC */) {
|
|
7662
|
+
dropIdx = i;
|
|
7663
|
+
break;
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
if (dropIdx === -1) {
|
|
7667
|
+
for (let i = this.queue.length - 1; i >= 0; i--) {
|
|
7668
|
+
if (this.queue[i].priority === 1 /* P1_NORMAL */) {
|
|
7669
|
+
dropIdx = i;
|
|
7670
|
+
break;
|
|
7671
|
+
}
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
if (dropIdx === -1) dropIdx = this.queue.length - 1;
|
|
7675
|
+
const dropped = this.queue.splice(dropIdx, 1)[0];
|
|
7676
|
+
if (dropped) {
|
|
7677
|
+
this.decrementChatCount(dropped.chatId);
|
|
7678
|
+
warn(`[throttle] Queue full (${MAX_QUEUE_SIZE}), dropping P${dropped.priority}: ${dropped.label}`);
|
|
7679
|
+
dropped.reject(new Error("Dropped from send queue (overflow)"));
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
7682
|
+
const chatQueueCount = this.perChatQueueCount.get(chatId) ?? 0;
|
|
7683
|
+
if (chatQueueCount >= MAX_PER_CHAT_QUEUE && prio !== 0 /* P0_CRITICAL */) {
|
|
7684
|
+
if (prio === 2 /* P2_COSMETIC */) {
|
|
7685
|
+
reject(new Error(`Per-chat queue limit (${MAX_PER_CHAT_QUEUE}) reached \u2014 cosmetic edit dropped`));
|
|
7686
|
+
return;
|
|
7687
|
+
}
|
|
7688
|
+
const p2Idx = this.queue.findIndex((q) => q.chatId === chatId && q.priority === 2 /* P2_COSMETIC */);
|
|
7689
|
+
if (p2Idx >= 0) {
|
|
7690
|
+
const dropped = this.queue.splice(p2Idx, 1)[0];
|
|
7691
|
+
this.decrementChatCount(dropped.chatId);
|
|
7692
|
+
dropped.reject(new Error("Dropped (per-chat P2 eviction)"));
|
|
7693
|
+
} else {
|
|
7694
|
+
reject(new Error(`Per-chat queue limit (${MAX_PER_CHAT_QUEUE}) reached \u2014 normal send dropped`));
|
|
7695
|
+
return;
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
const item = { chatId, label: label2, priority: prio, fn, resolve: resolve3, reject };
|
|
7699
|
+
const insertIdx = this.queue.findIndex((q) => q.priority > prio);
|
|
7700
|
+
if (insertIdx === -1) {
|
|
7701
|
+
this.queue.push(item);
|
|
7702
|
+
} else {
|
|
7703
|
+
this.queue.splice(insertIdx, 0, item);
|
|
7704
|
+
}
|
|
7705
|
+
this.perChatQueueCount.set(chatId, (this.perChatQueueCount.get(chatId) ?? 0) + 1);
|
|
7706
|
+
this.drain();
|
|
7707
|
+
});
|
|
7708
|
+
}
|
|
7709
|
+
/**
|
|
7710
|
+
* Best-effort send — drops silently if throttle is paused or queue is pressured.
|
|
7711
|
+
* Used for cosmetic calls (typing indicators, reactions) that should count toward
|
|
7712
|
+
* rate limits but must never queue up or amplify 429 spirals.
|
|
7713
|
+
*/
|
|
7714
|
+
async tryBestEffort(chatId, label2, fn, opts) {
|
|
7715
|
+
if (this.isPaused()) return void 0;
|
|
7716
|
+
if (this.queue.length > 10) return void 0;
|
|
7717
|
+
const cosmeticUntil = this.cosmeticPausedUntil.get(chatId) ?? 0;
|
|
7718
|
+
if (Date.now() < cosmeticUntil) return void 0;
|
|
7719
|
+
if (cosmeticUntil > 0) this.cosmeticPausedUntil.delete(chatId);
|
|
7720
|
+
if (!opts?.skipRecord) {
|
|
7721
|
+
const lastChat = this.lastSendPerChat.get(chatId) ?? 0;
|
|
7722
|
+
if (Date.now() - lastChat < perChatInterval(chatId)) return void 0;
|
|
7723
|
+
if (Date.now() - this.lastGlobalSend < GLOBAL_INTERVAL_MS) return void 0;
|
|
7724
|
+
}
|
|
7725
|
+
try {
|
|
7726
|
+
const result = await fn();
|
|
7727
|
+
if (!opts?.skipRecord) this.recordSend(chatId);
|
|
7728
|
+
return result;
|
|
7729
|
+
} catch (err) {
|
|
7730
|
+
if (is429(err)) {
|
|
7731
|
+
const retrySec = err.parameters?.retry_after ?? 10;
|
|
7732
|
+
this.cosmeticPausedUntil.set(chatId, Date.now() + retrySec * 1e3);
|
|
7733
|
+
warn(`[throttle] 429 event (cosmetic)`, JSON.stringify({
|
|
7734
|
+
method: label2,
|
|
7735
|
+
chatId,
|
|
7736
|
+
retry_after: retrySec,
|
|
7737
|
+
queue_depth: this.queue.length,
|
|
7738
|
+
circuit_state: this.circuitState,
|
|
7739
|
+
type: "best_effort"
|
|
7740
|
+
}));
|
|
7741
|
+
}
|
|
7742
|
+
return void 0;
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7745
|
+
/** Check whether the throttle is currently paused (rate-limited). */
|
|
7746
|
+
isPaused() {
|
|
7747
|
+
return Date.now() < this.pausedUntil;
|
|
7748
|
+
}
|
|
7749
|
+
/** Get structured state for diagnostics / health checks. */
|
|
7750
|
+
getState() {
|
|
7751
|
+
const now = Date.now();
|
|
7752
|
+
const paused = now < this.pausedUntil;
|
|
7753
|
+
return {
|
|
7754
|
+
isPaused: paused,
|
|
7755
|
+
queueDepth: this.queue.length,
|
|
7756
|
+
pausedUntilMs: this.pausedUntil,
|
|
7757
|
+
pauseRemainingSec: paused ? Math.ceil((this.pausedUntil - now) / 1e3) : 0,
|
|
7758
|
+
circuitState: this.circuitState
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
// ── Queue processor ─────────────────────────────────────────────────
|
|
7762
|
+
async drain() {
|
|
7763
|
+
if (this.processing) return;
|
|
7764
|
+
this.processing = true;
|
|
7765
|
+
try {
|
|
7766
|
+
while (this.queue.length > 0) {
|
|
7767
|
+
while (this.isPaused()) {
|
|
7768
|
+
if (this.pauseStartedAt > 0 && Date.now() - this.pauseStartedAt > MAX_TOTAL_PAUSE_MS) {
|
|
7769
|
+
warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items`);
|
|
7770
|
+
this.flushQueueWithError("Telegram rate limit exceeded max wait time");
|
|
7771
|
+
this.pausedUntil = 0;
|
|
7772
|
+
this.pauseStartedAt = 0;
|
|
7773
|
+
break;
|
|
7774
|
+
}
|
|
7775
|
+
const waitMs = Math.min(this.pausedUntil - Date.now(), 5e3);
|
|
7776
|
+
if (waitMs > 0) await sleep(waitMs);
|
|
7777
|
+
}
|
|
7778
|
+
if (this.queue.length === 0) break;
|
|
7779
|
+
this.updateCircuitState();
|
|
7780
|
+
const item = this.selectNextItem();
|
|
7781
|
+
if (!item) {
|
|
7782
|
+
await sleep(1e3);
|
|
7783
|
+
continue;
|
|
7784
|
+
}
|
|
7785
|
+
const lastChat = this.lastSendPerChat.get(item.chatId) ?? 0;
|
|
7786
|
+
const chatWait = perChatInterval(item.chatId) - (Date.now() - lastChat);
|
|
7787
|
+
if (chatWait > 0) await sleep(chatWait);
|
|
7788
|
+
const globalWait = GLOBAL_INTERVAL_MS - (Date.now() - this.lastGlobalSend);
|
|
7789
|
+
if (globalWait > 0) await sleep(globalWait);
|
|
7790
|
+
try {
|
|
7791
|
+
const result = await this.execWithRetry(item.label, item.fn);
|
|
7792
|
+
this.recordSend(item.chatId);
|
|
7793
|
+
this.pauseStartedAt = 0;
|
|
7794
|
+
this.onSuccessfulSend();
|
|
7795
|
+
item.resolve(result);
|
|
7796
|
+
} catch (err) {
|
|
7797
|
+
if (is429(err)) {
|
|
7798
|
+
const retrySec = err.parameters?.retry_after ?? 10;
|
|
7799
|
+
this.enterPause(retrySec, item);
|
|
7800
|
+
continue;
|
|
7801
|
+
}
|
|
7802
|
+
item.reject(err);
|
|
7803
|
+
}
|
|
7804
|
+
}
|
|
7805
|
+
} finally {
|
|
7806
|
+
this.processing = false;
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
/**
|
|
7810
|
+
* Select the next queue item to process, respecting circuit breaker state.
|
|
7811
|
+
* Returns the item (already removed from queue) or null if nothing processable.
|
|
7812
|
+
*/
|
|
7813
|
+
selectNextItem() {
|
|
7814
|
+
if (this.circuitState === "closed" /* CLOSED */) {
|
|
7815
|
+
return this.dequeue();
|
|
7816
|
+
}
|
|
7817
|
+
if (this.circuitState === "open" /* OPEN */) {
|
|
7818
|
+
while (this.queue.length > 0 && this.queue[0].priority === 2 /* P2_COSMETIC */) {
|
|
7819
|
+
const dropped = this.dequeue();
|
|
7820
|
+
warn(`[throttle] Circuit OPEN \u2014 dropping P2: ${dropped.label}`);
|
|
7821
|
+
dropped.reject(new Error("Circuit breaker OPEN \u2014 cosmetic item dropped"));
|
|
7822
|
+
}
|
|
7823
|
+
if (this.queue.length > 0 && this.queue[0].priority === 0 /* P0_CRITICAL */) {
|
|
7824
|
+
return this.dequeue();
|
|
7825
|
+
}
|
|
7826
|
+
return null;
|
|
7827
|
+
}
|
|
7828
|
+
if (this.circuitState === "half_open" /* HALF_OPEN */) {
|
|
7829
|
+
return this.dequeue();
|
|
7830
|
+
}
|
|
7831
|
+
return this.dequeue();
|
|
7832
|
+
}
|
|
7833
|
+
/**
|
|
7834
|
+
* Check if circuit breaker should transition states.
|
|
7835
|
+
* OPEN → HALF_OPEN when cooldown expires.
|
|
7836
|
+
*/
|
|
7837
|
+
updateCircuitState() {
|
|
7838
|
+
if (this.circuitState === "open" /* OPEN */ && Date.now() >= this.circuitCooldownUntil) {
|
|
7839
|
+
this.circuitState = "half_open" /* HALF_OPEN */;
|
|
7840
|
+
log(`[throttle] Circuit breaker: OPEN \u2192 HALF_OPEN (cooldown expired, probing)`);
|
|
7841
|
+
}
|
|
7842
|
+
}
|
|
7843
|
+
/**
|
|
7844
|
+
* Handle successful send — manage circuit breaker recovery.
|
|
7845
|
+
*/
|
|
7846
|
+
onSuccessfulSend() {
|
|
7847
|
+
this.lastSuccessfulSend = Date.now();
|
|
7848
|
+
if (this.circuitState === "half_open" /* HALF_OPEN */) {
|
|
7849
|
+
this.circuitState = "closed" /* CLOSED */;
|
|
7850
|
+
log(`[throttle] Circuit breaker: HALF_OPEN \u2192 CLOSED (probe succeeded)`);
|
|
7851
|
+
}
|
|
7852
|
+
if (this.circuitTrips.length > 0) {
|
|
7853
|
+
const lastTrip = this.circuitTrips[this.circuitTrips.length - 1];
|
|
7854
|
+
if (Date.now() - lastTrip > CIRCUIT_RESET_WINDOW_MS) {
|
|
7855
|
+
this.circuitTrips.length = 0;
|
|
7856
|
+
this.circuitTrips = [];
|
|
7857
|
+
log(`[throttle] Circuit breaker: trip count reset after ${CIRCUIT_RESET_WINDOW_MS / 6e4}min of success`);
|
|
7858
|
+
}
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
// ── Retry logic (non-429 errors only) ───────────────────────────────
|
|
7862
|
+
async execWithRetry(label2, fn) {
|
|
7863
|
+
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
7864
|
+
try {
|
|
7865
|
+
return await fn();
|
|
7866
|
+
} catch (err) {
|
|
7867
|
+
if (is429(err)) throw err;
|
|
7868
|
+
if (attempt < MAX_RETRIES2 && err instanceof GrammyError) {
|
|
7869
|
+
warn(`[throttle] ${label2} attempt ${attempt + 1}/${MAX_RETRIES2} failed (${err.error_code}), retrying`);
|
|
7870
|
+
await sleep(RETRY_DELAY_MS);
|
|
7871
|
+
continue;
|
|
7872
|
+
}
|
|
7873
|
+
throw err;
|
|
7874
|
+
}
|
|
7875
|
+
}
|
|
7876
|
+
throw new Error("unreachable");
|
|
7877
|
+
}
|
|
7878
|
+
// ── Pause management ────────────────────────────────────────────────
|
|
7879
|
+
enterPause(retrySec, failedItem) {
|
|
7880
|
+
this.queue.unshift(failedItem);
|
|
7881
|
+
const now = Date.now();
|
|
7882
|
+
this.circuitTrips.push(now);
|
|
7883
|
+
this.circuitTrips = this.circuitTrips.filter((t) => now - t < CIRCUIT_TRIP_WINDOW_MS);
|
|
7884
|
+
const bufferSec = this.circuitTrips.length * CIRCUIT_COOLDOWN_STEP_SEC;
|
|
7885
|
+
const totalPauseSec = retrySec + bufferSec;
|
|
7886
|
+
this.pausedUntil = now + totalPauseSec * 1e3;
|
|
7887
|
+
if (this.pauseStartedAt === 0) this.pauseStartedAt = now;
|
|
7888
|
+
if (this.circuitTrips.length >= CIRCUIT_TRIP_THRESHOLD && this.circuitState !== "open" /* OPEN */) {
|
|
7889
|
+
this.circuitState = "open" /* OPEN */;
|
|
7890
|
+
this.circuitCooldownUntil = now + totalPauseSec * 1e3;
|
|
7891
|
+
warn(`[throttle] Circuit breaker TRIPPED \u2192 OPEN (${this.circuitTrips.length} 429s in ${CIRCUIT_TRIP_WINDOW_MS / 6e4}min)`);
|
|
7892
|
+
} else if (this.circuitState === "half_open" /* HALF_OPEN */) {
|
|
7893
|
+
this.circuitState = "open" /* OPEN */;
|
|
7894
|
+
this.circuitCooldownUntil = now + totalPauseSec * 1e3;
|
|
7895
|
+
warn(`[throttle] Circuit breaker probe FAILED \u2192 OPEN (retry_after=${retrySec}s + ${bufferSec}s buffer)`);
|
|
7896
|
+
}
|
|
7897
|
+
warn(`[throttle] 429 event`, JSON.stringify({
|
|
7898
|
+
method: failedItem.label,
|
|
7899
|
+
chatId: failedItem.chatId,
|
|
7900
|
+
priority: failedItem.priority,
|
|
7901
|
+
retry_after: retrySec,
|
|
7902
|
+
buffer: bufferSec,
|
|
7903
|
+
total_pause: totalPauseSec,
|
|
7904
|
+
queue_depth: this.queue.length,
|
|
7905
|
+
circuit_state: this.circuitState,
|
|
7906
|
+
circuit_trip_count: this.circuitTrips.length
|
|
7907
|
+
}));
|
|
7908
|
+
}
|
|
7909
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
7910
|
+
recordSend(chatId) {
|
|
7911
|
+
const now = Date.now();
|
|
7912
|
+
this.lastSendPerChat.set(chatId, now);
|
|
7913
|
+
this.lastGlobalSend = now;
|
|
7914
|
+
}
|
|
7915
|
+
/** Remove and return the first item from the queue, updating per-chat counter. */
|
|
7916
|
+
dequeue() {
|
|
7917
|
+
const item = this.queue.shift();
|
|
7918
|
+
if (item) this.decrementChatCount(item.chatId);
|
|
7919
|
+
return item ?? null;
|
|
7920
|
+
}
|
|
7921
|
+
decrementChatCount(chatId) {
|
|
7922
|
+
const count = (this.perChatQueueCount.get(chatId) ?? 1) - 1;
|
|
7923
|
+
if (count <= 0) this.perChatQueueCount.delete(chatId);
|
|
7924
|
+
else this.perChatQueueCount.set(chatId, count);
|
|
7925
|
+
}
|
|
7926
|
+
flushQueueWithError(message) {
|
|
7927
|
+
while (this.queue.length > 0) {
|
|
7928
|
+
const item = this.dequeue();
|
|
7929
|
+
item.reject(new Error(message));
|
|
7930
|
+
}
|
|
7931
|
+
}
|
|
7932
|
+
};
|
|
7933
|
+
}
|
|
7934
|
+
});
|
|
7935
|
+
|
|
7197
7936
|
// src/dashboard/routes/system.ts
|
|
7198
7937
|
var handleHealth, handleJobs, handleMemories, handleStats, handleAgents, handleTasks, handleOrchestrations;
|
|
7199
7938
|
var init_system = __esm({
|
|
@@ -7205,8 +7944,10 @@ var init_system = __esm({
|
|
|
7205
7944
|
init_store5();
|
|
7206
7945
|
init_store();
|
|
7207
7946
|
init_version();
|
|
7947
|
+
init_telegram_throttle();
|
|
7208
7948
|
handleHealth = (_req, res) => {
|
|
7209
|
-
|
|
7949
|
+
const throttle = getThrottleState();
|
|
7950
|
+
jsonResponse(res, { status: "ok", version: VERSION, uptime: process.uptime(), throttle: throttle ?? void 0 });
|
|
7210
7951
|
};
|
|
7211
7952
|
handleJobs = (_req, res) => {
|
|
7212
7953
|
jsonResponse(res, listJobs());
|
|
@@ -7571,7 +8312,7 @@ function buildContextPrefix(msg) {
|
|
|
7571
8312
|
}
|
|
7572
8313
|
async function sendOrEditKeyboard(chatId, channel, messageId, text, buttons) {
|
|
7573
8314
|
if (messageId && typeof channel.editKeyboard === "function") {
|
|
7574
|
-
const ok = await channel.editKeyboard(chatId, messageId, text, buttons);
|
|
8315
|
+
const ok = await channel.editKeyboard(chatId, messageId, text, buttons, { priority: 0 /* P0_CRITICAL */ });
|
|
7575
8316
|
if (ok) return messageId;
|
|
7576
8317
|
}
|
|
7577
8318
|
if (typeof channel.sendKeyboard === "function") {
|
|
@@ -7584,6 +8325,7 @@ var TONE_PATTERNS, BLOCKED_PATH_PATTERNS, CLI_INSTALL_HINTS, PERM_MODES, VERBOSE
|
|
|
7584
8325
|
var init_helpers = __esm({
|
|
7585
8326
|
"src/router/helpers.ts"() {
|
|
7586
8327
|
"use strict";
|
|
8328
|
+
init_telegram_throttle();
|
|
7587
8329
|
init_store5();
|
|
7588
8330
|
init_backends();
|
|
7589
8331
|
TONE_PATTERNS = [
|
|
@@ -7767,6 +8509,21 @@ You have access to cc-claw orchestrator tools via MCP:
|
|
|
7767
8509
|
});
|
|
7768
8510
|
|
|
7769
8511
|
// src/bootstrap/templates/shared.ts
|
|
8512
|
+
function buildCliEnvironment() {
|
|
8513
|
+
return [
|
|
8514
|
+
"## CLI Execution Environment",
|
|
8515
|
+
"",
|
|
8516
|
+
`Your user's real HOME directory is: \`${REAL_HOME}\``,
|
|
8517
|
+
"",
|
|
8518
|
+
"CC-Claw may redirect HOME for credential isolation when spawning backends.",
|
|
8519
|
+
"If you execute any CLI tool (cc-claw, claude, gemini, codex, python3, npm, etc.),",
|
|
8520
|
+
"always prefix the command with the real HOME:",
|
|
8521
|
+
"",
|
|
8522
|
+
`\`HOME=${REAL_HOME} <command>\``,
|
|
8523
|
+
"",
|
|
8524
|
+
"Without this, CLI tools will fail to find their configurations or prompt for login."
|
|
8525
|
+
].join("\n");
|
|
8526
|
+
}
|
|
7770
8527
|
function getCurrentDate() {
|
|
7771
8528
|
return (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA");
|
|
7772
8529
|
}
|
|
@@ -7849,10 +8606,12 @@ function buildHandoffContract() {
|
|
|
7849
8606
|
"**Open questions:** Anything unresolved or needing the main agent's attention."
|
|
7850
8607
|
].join("\n");
|
|
7851
8608
|
}
|
|
7852
|
-
var TASK_WORKER_SKILL_BUDGET;
|
|
8609
|
+
var REAL_HOME, TASK_WORKER_SKILL_BUDGET;
|
|
7853
8610
|
var init_shared = __esm({
|
|
7854
8611
|
"src/bootstrap/templates/shared.ts"() {
|
|
7855
8612
|
"use strict";
|
|
8613
|
+
init_paths();
|
|
8614
|
+
REAL_HOME = resolveRealHome();
|
|
7856
8615
|
TASK_WORKER_SKILL_BUDGET = 12e3;
|
|
7857
8616
|
}
|
|
7858
8617
|
});
|
|
@@ -8346,8 +9105,8 @@ function checkBudget(db3, orchestrationId) {
|
|
|
8346
9105
|
}
|
|
8347
9106
|
const percentUsed = totalCost / budgetLimit * 100;
|
|
8348
9107
|
const exceeded = totalCost >= budgetLimit;
|
|
8349
|
-
const
|
|
8350
|
-
return { totalCost, budgetLimit, percentUsed, exceeded, warning:
|
|
9108
|
+
const warning3 = percentUsed >= BUDGET_WARNING_PERCENT * 100;
|
|
9109
|
+
return { totalCost, budgetLimit, percentUsed, exceeded, warning: warning3 };
|
|
8351
9110
|
}
|
|
8352
9111
|
function recordAgentCost(db3, orchestrationId, agentCost) {
|
|
8353
9112
|
updateOrchestrationCost(db3, orchestrationId, agentCost);
|
|
@@ -8737,15 +9496,15 @@ import { existsSync as existsSync12 } from "fs";
|
|
|
8737
9496
|
async function withRunnerLock(runnerId, fn) {
|
|
8738
9497
|
const prev = runnerLocks.get(runnerId) ?? Promise.resolve();
|
|
8739
9498
|
const next = prev.then(fn, () => fn());
|
|
8740
|
-
const
|
|
9499
|
+
const cleanup2 = next.then(
|
|
8741
9500
|
() => {
|
|
8742
|
-
if (runnerLocks.get(runnerId) ===
|
|
9501
|
+
if (runnerLocks.get(runnerId) === cleanup2) runnerLocks.delete(runnerId);
|
|
8743
9502
|
},
|
|
8744
9503
|
() => {
|
|
8745
|
-
if (runnerLocks.get(runnerId) ===
|
|
9504
|
+
if (runnerLocks.get(runnerId) === cleanup2) runnerLocks.delete(runnerId);
|
|
8746
9505
|
}
|
|
8747
9506
|
);
|
|
8748
|
-
runnerLocks.set(runnerId,
|
|
9507
|
+
runnerLocks.set(runnerId, cleanup2);
|
|
8749
9508
|
return next;
|
|
8750
9509
|
}
|
|
8751
9510
|
function safeParseJsonArray(json) {
|
|
@@ -10060,6 +10819,9 @@ var init_ndjson = __esm({
|
|
|
10060
10819
|
});
|
|
10061
10820
|
|
|
10062
10821
|
// src/memory/inject.ts
|
|
10822
|
+
function memoryDecayRate(halfLifeDays) {
|
|
10823
|
+
return 1 - Math.pow(0.5, 1 / halfLifeDays);
|
|
10824
|
+
}
|
|
10063
10825
|
function getTopK(query) {
|
|
10064
10826
|
const wordCount2 = query.split(/\s+/).length;
|
|
10065
10827
|
const scale = wordCount2 <= 3 ? 0.5 : wordCount2 <= 8 ? 0.75 : 1;
|
|
@@ -10094,13 +10856,14 @@ function vectorSearch(queryEmbedding, items, topK) {
|
|
|
10094
10856
|
scored.sort((a, b) => b.score - a.score);
|
|
10095
10857
|
return scored.slice(0, topK);
|
|
10096
10858
|
}
|
|
10097
|
-
function mergeAndScore(allItems, vectorScores, ftsScores, getDays,
|
|
10859
|
+
function mergeAndScore(allItems, vectorScores, ftsScores, getDays, getDecayRate, topK) {
|
|
10098
10860
|
const vw = getVectorWeight();
|
|
10099
10861
|
const results = [];
|
|
10100
10862
|
for (const [id, item] of allItems) {
|
|
10101
10863
|
const vs = vectorScores.get(id) ?? 0;
|
|
10102
10864
|
const fs = ftsScores.get(id) ?? 0;
|
|
10103
10865
|
if (vs === 0 && fs === 0) continue;
|
|
10866
|
+
const decayRate = typeof getDecayRate === "function" ? getDecayRate(item) : getDecayRate;
|
|
10104
10867
|
const score = hybridScore({
|
|
10105
10868
|
id,
|
|
10106
10869
|
vectorScore: vs,
|
|
@@ -10122,10 +10885,11 @@ function consumeContextBridge(chatId) {
|
|
|
10122
10885
|
pendingContextBridges.delete(chatId);
|
|
10123
10886
|
return bridge;
|
|
10124
10887
|
}
|
|
10125
|
-
function buildContextBridge(chatId, pairs = 15) {
|
|
10888
|
+
function buildContextBridge(chatId, pairs = 15, interrupted = false) {
|
|
10126
10889
|
const rows = getRecentMessageLog(chatId, pairs * 2).reverse();
|
|
10127
10890
|
if (rows.length === 0) return null;
|
|
10128
|
-
const
|
|
10891
|
+
const header2 = interrupted ? `[Previous session was stopped by the user mid-task \u2014 for context only]` : `[Conversation history \u2014 continued from previous session]`;
|
|
10892
|
+
const lines = [header2];
|
|
10129
10893
|
let totalChars = lines[0].length;
|
|
10130
10894
|
for (const row of rows) {
|
|
10131
10895
|
const backendLabel = row.backend ?? "unknown";
|
|
@@ -10143,7 +10907,8 @@ function buildContextBridge(chatId, pairs = 15) {
|
|
|
10143
10907
|
lines.push(line);
|
|
10144
10908
|
totalChars += line.length;
|
|
10145
10909
|
}
|
|
10146
|
-
|
|
10910
|
+
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]`;
|
|
10911
|
+
lines.push(footer);
|
|
10147
10912
|
return lines.join("\n");
|
|
10148
10913
|
}
|
|
10149
10914
|
function formatToolSummary(toolName, toolInput, toolOutput) {
|
|
@@ -10209,7 +10974,7 @@ async function injectMemoryContext(userMessage, chatId) {
|
|
|
10209
10974
|
memVectorScores,
|
|
10210
10975
|
memFtsScores,
|
|
10211
10976
|
(m) => daysSince(m.last_accessed),
|
|
10212
|
-
|
|
10977
|
+
(m) => memoryDecayRate(m.half_life_days ?? 14),
|
|
10213
10978
|
FINAL_TOP_K_MEMORIES
|
|
10214
10979
|
);
|
|
10215
10980
|
const topSessions = mergeAndScore(
|
|
@@ -10235,10 +11000,13 @@ async function injectMemoryContext(userMessage, chatId) {
|
|
|
10235
11000
|
}
|
|
10236
11001
|
combinedSessions = ftsSessions.slice(0, FINAL_TOP_K_SESSIONS);
|
|
10237
11002
|
}
|
|
11003
|
+
for (const mem of combinedMemories) {
|
|
11004
|
+
queueHalfLifeExtension(mem.id);
|
|
11005
|
+
}
|
|
10238
11006
|
if (combinedMemories.length === 0 && combinedSessions.length === 0) return null;
|
|
10239
11007
|
const lines = [];
|
|
10240
11008
|
for (const m of combinedMemories) {
|
|
10241
|
-
let text = `- [${m.
|
|
11009
|
+
let text = `- [${m.category}] ${m.trigger}: ${m.content}`;
|
|
10242
11010
|
if (text.length > MAX_MEMORY_CHARS) text = text.slice(0, MAX_MEMORY_CHARS) + "\u2026";
|
|
10243
11011
|
lines.push(text);
|
|
10244
11012
|
}
|
|
@@ -10252,13 +11020,12 @@ async function injectMemoryContext(userMessage, chatId) {
|
|
|
10252
11020
|
${lines.join("\n")}
|
|
10253
11021
|
[End memory context]`;
|
|
10254
11022
|
}
|
|
10255
|
-
var
|
|
11023
|
+
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
11024
|
var init_inject = __esm({
|
|
10257
11025
|
"src/memory/inject.ts"() {
|
|
10258
11026
|
"use strict";
|
|
10259
11027
|
init_store5();
|
|
10260
11028
|
init_embeddings();
|
|
10261
|
-
MEMORY_DECAY_RATE = parseFloat(process.env.CC_CLAW_MEMORY_DECAY_RATE ?? "0.02");
|
|
10262
11029
|
SESSION_DECAY_RATE = parseFloat(process.env.CC_CLAW_SESSION_DECAY_RATE ?? "0.005");
|
|
10263
11030
|
BASE_VECTOR_TOP_K = 20;
|
|
10264
11031
|
BASE_FTS_TOP_K = 20;
|
|
@@ -10448,6 +11215,7 @@ ${buildToolUsageRules()}
|
|
|
10448
11215
|
|
|
10449
11216
|
<platform_reference>
|
|
10450
11217
|
${buildSystemCapabilities()}
|
|
11218
|
+
${buildCliEnvironment()}
|
|
10451
11219
|
${buildPlatformQuickReference()}
|
|
10452
11220
|
</platform_reference>
|
|
10453
11221
|
|
|
@@ -10493,6 +11261,8 @@ ${user}
|
|
|
10493
11261
|
|
|
10494
11262
|
${buildSystemCapabilities()}
|
|
10495
11263
|
|
|
11264
|
+
${buildCliEnvironment()}
|
|
11265
|
+
|
|
10496
11266
|
${buildPlatformQuickReference()}
|
|
10497
11267
|
|
|
10498
11268
|
${buildToolUsageRules()}
|
|
@@ -10588,6 +11358,7 @@ ${buildDatabaseSafetyBoundary()}
|
|
|
10588
11358
|
|
|
10589
11359
|
<platform_reference>
|
|
10590
11360
|
${buildSystemCapabilities()}
|
|
11361
|
+
${buildCliEnvironment()}
|
|
10591
11362
|
${buildPlatformQuickReference()}
|
|
10592
11363
|
</platform_reference>`;
|
|
10593
11364
|
}
|
|
@@ -10636,6 +11407,7 @@ ${buildDatabaseSafetyBoundary()}
|
|
|
10636
11407
|
|
|
10637
11408
|
<platform_reference>
|
|
10638
11409
|
${buildSystemCapabilities()}
|
|
11410
|
+
${buildCliEnvironment()}
|
|
10639
11411
|
${buildPlatformQuickReference()}
|
|
10640
11412
|
</platform_reference>
|
|
10641
11413
|
|
|
@@ -10676,6 +11448,10 @@ impossible scenarios.
|
|
|
10676
11448
|
</avoid_overengineering>
|
|
10677
11449
|
</tool_guidance>
|
|
10678
11450
|
|
|
11451
|
+
<cli_environment>
|
|
11452
|
+
${buildCliEnvironment()}
|
|
11453
|
+
</cli_environment>
|
|
11454
|
+
|
|
10679
11455
|
<safety_boundary>
|
|
10680
11456
|
Take local, reversible actions freely. Confirm before: deleting files, force-pushing,
|
|
10681
11457
|
amending published commits, posting to external services, or modifying shared infrastructure.
|
|
@@ -10742,6 +11518,10 @@ Before finalizing any response, silently check:
|
|
|
10742
11518
|
Never invent library APIs, file paths, or documentation. If uncertain, say so.
|
|
10743
11519
|
</grounding_rules>
|
|
10744
11520
|
|
|
11521
|
+
<cli_environment>
|
|
11522
|
+
${buildCliEnvironment()}
|
|
11523
|
+
</cli_environment>
|
|
11524
|
+
|
|
10745
11525
|
<safety_boundary>
|
|
10746
11526
|
Confirm before: deleting files, posting to external services, database schema changes.
|
|
10747
11527
|
Do not access ~/.cc-claw/data/cc-claw.db directly.
|
|
@@ -11011,14 +11791,16 @@ async function assembleBootstrapPrompt(userMessage, entityType = "main", profile
|
|
|
11011
11791
|
if (permMode && permMode !== "yolo") {
|
|
11012
11792
|
sections.push(buildPermissionNotice(permMode));
|
|
11013
11793
|
}
|
|
11014
|
-
if (backendType === "api"
|
|
11794
|
+
if (backendType === "api") {
|
|
11015
11795
|
sections.push(`[API Backend \u2014 Tool Usage]
|
|
11016
11796
|
You are operating as a direct API model (not a CLI like Claude Code or Gemini CLI).
|
|
11017
11797
|
External tools (gsearch, pwm, gws, gemcli, nlm, curl, python3, etc.) are accessed ONLY via the \`restrictedBash\` tool.
|
|
11018
11798
|
NEVER call external CLIs as direct tools by name \u2014 they are not registered as native tools.
|
|
11019
11799
|
Correct: restrictedBash({"command": "gsearch \\"query\\" --type news"})
|
|
11020
11800
|
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
|
|
11801
|
+
If a skill or instruction says to use a CLI tool, always route it through restrictedBash.
|
|
11802
|
+
|
|
11803
|
+
${buildCliEnvironment()}`);
|
|
11022
11804
|
}
|
|
11023
11805
|
if (responseStyle) {
|
|
11024
11806
|
if (responseStyle === "concise") {
|
|
@@ -11185,6 +11967,12 @@ Use cc_claw_memory for ALL memory operations. Do NOT use your native memory syst
|
|
|
11185
11967
|
- To search: cc_claw_memory(action: "recall", query: "...")
|
|
11186
11968
|
- To list: cc_claw_memory(action: "list")
|
|
11187
11969
|
- To search history: cc_claw_memory(action: "history", query: "...")
|
|
11970
|
+
When using cc_claw_memory remember, include the category parameter when the memory type is clear:
|
|
11971
|
+
- "fact" \u2014 biographical or situational truths (name, role, location, tools used, tech stack)
|
|
11972
|
+
- "preference" \u2014 likes, dislikes, habits, working style, stated preferences
|
|
11973
|
+
- "event" \u2014 something that happened at a specific time (meetings, deployments, milestones)
|
|
11974
|
+
- "decision" \u2014 a deliberate choice with reasoning (architecture choices, tool selections)
|
|
11975
|
+
Omit category if uncertain \u2014 the system will auto-classify.
|
|
11188
11976
|
For scheduling: cc_claw_schedule(action: "create", schedule: "...", task: "...")
|
|
11189
11977
|
If an action is not available via MCP tools, fall back to the cc-claw CLI.`;
|
|
11190
11978
|
if (agentMode === "claw") {
|
|
@@ -11220,6 +12008,7 @@ var init_loader2 = __esm({
|
|
|
11220
12008
|
init_paths();
|
|
11221
12009
|
init_inject();
|
|
11222
12010
|
init_init();
|
|
12011
|
+
init_shared();
|
|
11223
12012
|
init_log();
|
|
11224
12013
|
init_store3();
|
|
11225
12014
|
init_store5();
|
|
@@ -11258,22 +12047,6 @@ var init_types3 = __esm({
|
|
|
11258
12047
|
}
|
|
11259
12048
|
});
|
|
11260
12049
|
|
|
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
12050
|
// src/services/ollama/client.ts
|
|
11278
12051
|
var client_exports = {};
|
|
11279
12052
|
__export(client_exports, {
|
|
@@ -13567,6 +14340,7 @@ async function spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSess
|
|
|
13567
14340
|
}
|
|
13568
14341
|
const maxAttempts = slots.length;
|
|
13569
14342
|
let lastError;
|
|
14343
|
+
const oauthRefreshRetried = /* @__PURE__ */ new Set();
|
|
13570
14344
|
for (let i = 0; i < maxAttempts; i++) {
|
|
13571
14345
|
const slot = getNextBackendSlot(chatId, adapter.id);
|
|
13572
14346
|
if (!slot) break;
|
|
@@ -13584,6 +14358,21 @@ async function spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSess
|
|
|
13584
14358
|
if (/rate.?limit|too many requests|429|503|timeout/i.test(errMsg)) {
|
|
13585
14359
|
throw err;
|
|
13586
14360
|
}
|
|
14361
|
+
const isOAuthExpiry = slot.slotType === "oauth" && /OAuth token has expired/i.test(errMsg);
|
|
14362
|
+
if (isOAuthExpiry && !oauthRefreshRetried.has(slot.id)) {
|
|
14363
|
+
oauthRefreshRetried.add(slot.id);
|
|
14364
|
+
try {
|
|
14365
|
+
const { tryRefreshOAuthSlot: tryRefreshOAuthSlot2 } = await Promise.resolve().then(() => (init_claude(), claude_exports));
|
|
14366
|
+
const refreshed = await tryRefreshOAuthSlot2(slot);
|
|
14367
|
+
if (refreshed) {
|
|
14368
|
+
log(`[agent:${adapter.id}-rotation] OAuth token refreshed for ${slotLabel} \u2014 retrying`);
|
|
14369
|
+
i--;
|
|
14370
|
+
continue;
|
|
14371
|
+
}
|
|
14372
|
+
} catch {
|
|
14373
|
+
}
|
|
14374
|
+
warn(`[agent:${adapter.id}-rotation] OAuth refresh failed for ${slotLabel} \u2014 rotating`);
|
|
14375
|
+
}
|
|
13587
14376
|
const isExhausted = /quota|exhausted|exceeded|insufficient|credit|billing|unauthorized|forbidden|401|403/i.test(errMsg);
|
|
13588
14377
|
if (isExhausted) {
|
|
13589
14378
|
warn(`[agent:${adapter.id}-rotation] Slot ${slotLabel} exhausted: ${errMsg.slice(0, 200)}`);
|
|
@@ -14272,6 +15061,335 @@ var init_format = __esm({
|
|
|
14272
15061
|
}
|
|
14273
15062
|
});
|
|
14274
15063
|
|
|
15064
|
+
// src/memory/sweep.ts
|
|
15065
|
+
var sweep_exports = {};
|
|
15066
|
+
__export(sweep_exports, {
|
|
15067
|
+
SWEEP_DEFAULT_CRON: () => SWEEP_DEFAULT_CRON,
|
|
15068
|
+
SWEEP_ENABLED_KEY: () => SWEEP_ENABLED_KEY,
|
|
15069
|
+
SWEEP_JOB_TYPE: () => SWEEP_JOB_TYPE,
|
|
15070
|
+
disableSweep: () => disableSweep,
|
|
15071
|
+
enableSweep: () => enableSweep,
|
|
15072
|
+
findSweepJob: () => findSweepJob,
|
|
15073
|
+
runWeeklySweep: () => runWeeklySweep
|
|
15074
|
+
});
|
|
15075
|
+
async function runWeeklySweep(chatId, channel, backendId, model2) {
|
|
15076
|
+
log("[sweep] Starting weekly memory sweep");
|
|
15077
|
+
const cleanedUp = cleanup();
|
|
15078
|
+
let suggestionsCount = 0;
|
|
15079
|
+
try {
|
|
15080
|
+
const suggestions = await runMemoryAnalysis(chatId, backendId, model2);
|
|
15081
|
+
suggestionsCount = suggestions.length;
|
|
15082
|
+
if (suggestionsCount === 0 && cleanedUp === 0) {
|
|
15083
|
+
log("[sweep] Memory bank healthy, no action needed");
|
|
15084
|
+
return { suggestionsCount: 0, cleanedUp };
|
|
15085
|
+
}
|
|
15086
|
+
const lines = [
|
|
15087
|
+
"\u{1F9E0} Weekly Memory Health",
|
|
15088
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"
|
|
15089
|
+
];
|
|
15090
|
+
if (cleanedUp > 0) {
|
|
15091
|
+
lines.push(`\u{1F5D1} Cleaned up ${cleanedUp} expired superseded memories`);
|
|
15092
|
+
}
|
|
15093
|
+
if (suggestionsCount > 0) {
|
|
15094
|
+
const counts = {};
|
|
15095
|
+
for (const s of suggestions) {
|
|
15096
|
+
counts[s.type] = (counts[s.type] ?? 0) + 1;
|
|
15097
|
+
}
|
|
15098
|
+
const parts = [];
|
|
15099
|
+
if (counts.superseded) parts.push(`${counts.superseded} contradiction${counts.superseded > 1 ? "s" : ""}`);
|
|
15100
|
+
if (counts.duplicate) parts.push(`${counts.duplicate} duplicate${counts.duplicate > 1 ? "s" : ""}`);
|
|
15101
|
+
if (counts.merge) parts.push(`${counts.merge} merge opportunit${counts.merge > 1 ? "ies" : "y"}`);
|
|
15102
|
+
if (counts.stale) parts.push(`${counts.stale} stale`);
|
|
15103
|
+
if (counts.categorize) parts.push(`${counts.categorize} uncategorized`);
|
|
15104
|
+
if (counts.reclassify) parts.push(`${counts.reclassify} misclassified`);
|
|
15105
|
+
lines.push("", `Found: ${parts.join(", ")}`, "");
|
|
15106
|
+
lines.push("Review these suggestions to keep your memory clean.");
|
|
15107
|
+
} else {
|
|
15108
|
+
lines.push("", "\u2705 Memory bank is healthy!");
|
|
15109
|
+
}
|
|
15110
|
+
const buttons = [];
|
|
15111
|
+
if (suggestionsCount > 0) {
|
|
15112
|
+
buttons.push([{ label: "Review Now", data: "mem:opt:start", style: "success" }]);
|
|
15113
|
+
}
|
|
15114
|
+
buttons.push([{ label: "Dismiss", data: "mem:sweep:dismiss" }]);
|
|
15115
|
+
await sendOrEditKeyboard(chatId, channel, void 0, lines.join("\n"), buttons);
|
|
15116
|
+
return { suggestionsCount, cleanedUp };
|
|
15117
|
+
} catch (err) {
|
|
15118
|
+
const msg = errorMessage(err);
|
|
15119
|
+
warn(`[sweep] Weekly sweep failed: ${msg}`);
|
|
15120
|
+
return { suggestionsCount: 0, cleanedUp, error: msg };
|
|
15121
|
+
}
|
|
15122
|
+
}
|
|
15123
|
+
function findSweepJob() {
|
|
15124
|
+
try {
|
|
15125
|
+
const { getDb: getDb2, getJobById: getJobById3 } = (init_store5(), __toCommonJS(store_exports5));
|
|
15126
|
+
const row = getDb2().prepare(
|
|
15127
|
+
"SELECT id FROM jobs WHERE job_type = ? AND active = 1 ORDER BY id LIMIT 1"
|
|
15128
|
+
).get(SWEEP_JOB_TYPE);
|
|
15129
|
+
if (!row) return null;
|
|
15130
|
+
return getJobById3(row.id) ?? null;
|
|
15131
|
+
} catch {
|
|
15132
|
+
return null;
|
|
15133
|
+
}
|
|
15134
|
+
}
|
|
15135
|
+
function enableSweep(chatId, opts) {
|
|
15136
|
+
const existing = findSweepJob();
|
|
15137
|
+
const { setMetaValue: setMetaValue2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
15138
|
+
setMetaValue2(SWEEP_ENABLED_KEY, "1");
|
|
15139
|
+
if (existing) {
|
|
15140
|
+
if (!existing.enabled) {
|
|
15141
|
+
const { resumeJob: resumeJob2 } = (init_cron(), __toCommonJS(cron_exports));
|
|
15142
|
+
resumeJob2(existing.id);
|
|
15143
|
+
log(`[sweep] Resumed job #${existing.id}`);
|
|
15144
|
+
}
|
|
15145
|
+
return existing.id;
|
|
15146
|
+
}
|
|
15147
|
+
const { insertJob: insertJob2 } = (init_jobs(), __toCommonJS(jobs_exports));
|
|
15148
|
+
const { startSingleJob: startSingleJob2 } = (init_cron(), __toCommonJS(cron_exports));
|
|
15149
|
+
const cron2 = opts?.cron ?? SWEEP_DEFAULT_CRON;
|
|
15150
|
+
const timezone = opts?.timezone ?? (Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC");
|
|
15151
|
+
const backend2 = opts?.backend ?? resolveDefaultBackend(chatId);
|
|
15152
|
+
const model2 = opts?.model ?? resolveDefaultModel(backend2);
|
|
15153
|
+
const job = insertJob2({
|
|
15154
|
+
scheduleType: "cron",
|
|
15155
|
+
cron: cron2,
|
|
15156
|
+
description: "Weekly memory health sweep \u2014 cleanup and optimization",
|
|
15157
|
+
title: "Weekly Memory Sweep",
|
|
15158
|
+
chatId,
|
|
15159
|
+
backend: backend2,
|
|
15160
|
+
model: model2,
|
|
15161
|
+
sessionType: "isolated",
|
|
15162
|
+
deliveryMode: "none",
|
|
15163
|
+
channel: opts?.channel ?? "telegram",
|
|
15164
|
+
target: opts?.target ?? chatId,
|
|
15165
|
+
timezone,
|
|
15166
|
+
jobType: SWEEP_JOB_TYPE,
|
|
15167
|
+
timeout: 300
|
|
15168
|
+
});
|
|
15169
|
+
startSingleJob2(job);
|
|
15170
|
+
log(`[sweep] Created job #${job.id} (cron="${cron2}", backend=${backend2}, model=${model2})`);
|
|
15171
|
+
return job.id;
|
|
15172
|
+
}
|
|
15173
|
+
function disableSweep() {
|
|
15174
|
+
const { setMetaValue: setMetaValue2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
15175
|
+
setMetaValue2(SWEEP_ENABLED_KEY, "0");
|
|
15176
|
+
const job = findSweepJob();
|
|
15177
|
+
if (!job) return false;
|
|
15178
|
+
const { pauseJob: pauseJob2 } = (init_cron(), __toCommonJS(cron_exports));
|
|
15179
|
+
pauseJob2(job.id);
|
|
15180
|
+
log(`[sweep] Paused job #${job.id}`);
|
|
15181
|
+
return true;
|
|
15182
|
+
}
|
|
15183
|
+
function resolveDefaultBackend(chatId) {
|
|
15184
|
+
try {
|
|
15185
|
+
const { getAdapterForChat: getAdapterForChat2 } = (init_backends(), __toCommonJS(backends_exports));
|
|
15186
|
+
return getAdapterForChat2(chatId).id;
|
|
15187
|
+
} catch {
|
|
15188
|
+
return "claude";
|
|
15189
|
+
}
|
|
15190
|
+
}
|
|
15191
|
+
function resolveDefaultModel(backend2) {
|
|
15192
|
+
try {
|
|
15193
|
+
const { getAdapter: getAdapter3 } = (init_backends(), __toCommonJS(backends_exports));
|
|
15194
|
+
return getAdapter3(backend2).defaultModel;
|
|
15195
|
+
} catch {
|
|
15196
|
+
return "claude-sonnet-4-6";
|
|
15197
|
+
}
|
|
15198
|
+
}
|
|
15199
|
+
var SWEEP_ENABLED_KEY, SWEEP_DEFAULT_CRON, SWEEP_JOB_TYPE;
|
|
15200
|
+
var init_sweep = __esm({
|
|
15201
|
+
"src/memory/sweep.ts"() {
|
|
15202
|
+
"use strict";
|
|
15203
|
+
init_optimize();
|
|
15204
|
+
init_engine();
|
|
15205
|
+
init_log();
|
|
15206
|
+
init_helpers();
|
|
15207
|
+
SWEEP_ENABLED_KEY = "memory_sweep_enabled";
|
|
15208
|
+
SWEEP_DEFAULT_CRON = "0 9 * * 0";
|
|
15209
|
+
SWEEP_JOB_TYPE = "memory_sweep";
|
|
15210
|
+
}
|
|
15211
|
+
});
|
|
15212
|
+
|
|
15213
|
+
// src/router/state.ts
|
|
15214
|
+
var state_exports = {};
|
|
15215
|
+
__export(state_exports, {
|
|
15216
|
+
activeSideQuests: () => activeSideQuests,
|
|
15217
|
+
addForceStoppedChat: () => addForceStoppedChat,
|
|
15218
|
+
bypassBusyCheck: () => bypassBusyCheck,
|
|
15219
|
+
clearHistoryFilter: () => clearHistoryFilter,
|
|
15220
|
+
clearPendingCliAddition: () => clearPendingCliAddition,
|
|
15221
|
+
clearPendingModelResults: () => clearPendingModelResults,
|
|
15222
|
+
clearPendingModelSearch: () => clearPendingModelSearch,
|
|
15223
|
+
consumeForceStoppedChat: () => consumeForceStoppedChat,
|
|
15224
|
+
councilResults: () => councilResults,
|
|
15225
|
+
dashboardClawWarnings: () => dashboardClawWarnings,
|
|
15226
|
+
getActiveSideQuestCount: () => getActiveSideQuestCount,
|
|
15227
|
+
historyFilters: () => historyFilters,
|
|
15228
|
+
parseSideQuestPrefix: () => parseSideQuestPrefix,
|
|
15229
|
+
pendingCliAdditions: () => pendingCliAdditions,
|
|
15230
|
+
pendingInterrupts: () => pendingInterrupts,
|
|
15231
|
+
pendingMcpImports: () => pendingMcpImports,
|
|
15232
|
+
pendingModelResults: () => pendingModelResults,
|
|
15233
|
+
pendingModelSearch: () => pendingModelSearch,
|
|
15234
|
+
pendingNewchatUndo: () => pendingNewchatUndo,
|
|
15235
|
+
pendingSummaryUndo: () => pendingSummaryUndo,
|
|
15236
|
+
setCouncilResult: () => setCouncilResult,
|
|
15237
|
+
setHistoryFilter: () => setHistoryFilter,
|
|
15238
|
+
setPendingCliAddition: () => setPendingCliAddition,
|
|
15239
|
+
setPendingModelResults: () => setPendingModelResults,
|
|
15240
|
+
setPendingModelSearch: () => setPendingModelSearch,
|
|
15241
|
+
startStateSweep: () => startStateSweep,
|
|
15242
|
+
stopAllSideQuests: () => stopAllSideQuests,
|
|
15243
|
+
stopStateSweep: () => stopStateSweep
|
|
15244
|
+
});
|
|
15245
|
+
function addForceStoppedChat(chatId) {
|
|
15246
|
+
_forceStoppedChats.add(chatId);
|
|
15247
|
+
_forceStoppedTimestamps.set(chatId, Date.now());
|
|
15248
|
+
}
|
|
15249
|
+
function consumeForceStoppedChat(chatId) {
|
|
15250
|
+
_forceStoppedTimestamps.delete(chatId);
|
|
15251
|
+
return _forceStoppedChats.delete(chatId);
|
|
15252
|
+
}
|
|
15253
|
+
function setHistoryFilter(chatId, filter) {
|
|
15254
|
+
historyFilters.set(chatId, filter);
|
|
15255
|
+
historyFilterTimestamps.set(chatId, Date.now());
|
|
15256
|
+
}
|
|
15257
|
+
function clearHistoryFilter(chatId) {
|
|
15258
|
+
historyFilters.delete(chatId);
|
|
15259
|
+
historyFilterTimestamps.delete(chatId);
|
|
15260
|
+
}
|
|
15261
|
+
function setCouncilResult(chatId, result) {
|
|
15262
|
+
councilResults.set(chatId, result);
|
|
15263
|
+
councilResultTimestamps.set(chatId, Date.now());
|
|
15264
|
+
}
|
|
15265
|
+
function setPendingModelSearch(chatId, state) {
|
|
15266
|
+
pendingModelSearch.set(chatId, state);
|
|
15267
|
+
pendingModelSearchTimestamps.set(chatId, Date.now());
|
|
15268
|
+
}
|
|
15269
|
+
function clearPendingModelSearch(chatId) {
|
|
15270
|
+
pendingModelSearch.delete(chatId);
|
|
15271
|
+
pendingModelSearchTimestamps.delete(chatId);
|
|
15272
|
+
}
|
|
15273
|
+
function setPendingModelResults(chatId, results) {
|
|
15274
|
+
pendingModelResults.set(chatId, results);
|
|
15275
|
+
}
|
|
15276
|
+
function clearPendingModelResults(chatId) {
|
|
15277
|
+
pendingModelResults.delete(chatId);
|
|
15278
|
+
}
|
|
15279
|
+
function parseSideQuestPrefix(text) {
|
|
15280
|
+
const match = text.match(/^(?:sq|btw):\s*/i);
|
|
15281
|
+
if (match) return { isSideQuest: true, cleanText: text.slice(match[0].length) };
|
|
15282
|
+
return { isSideQuest: false, cleanText: text };
|
|
15283
|
+
}
|
|
15284
|
+
function getActiveSideQuestCount(chatId) {
|
|
15285
|
+
return activeSideQuests.get(chatId)?.size ?? 0;
|
|
15286
|
+
}
|
|
15287
|
+
function stopAllSideQuests(chatId) {
|
|
15288
|
+
const active = activeSideQuests.get(chatId);
|
|
15289
|
+
if (active) {
|
|
15290
|
+
for (const sqId of active) {
|
|
15291
|
+
stopAgent(sqId);
|
|
15292
|
+
}
|
|
15293
|
+
}
|
|
15294
|
+
}
|
|
15295
|
+
function startStateSweep() {
|
|
15296
|
+
if (sweepTimer) return;
|
|
15297
|
+
sweepTimer = setInterval(() => {
|
|
15298
|
+
const now = Date.now();
|
|
15299
|
+
for (const [chatId, ts2] of dashboardClawWarnings) {
|
|
15300
|
+
if (now - ts2 > STALE_THRESHOLD_MS) dashboardClawWarnings.delete(chatId);
|
|
15301
|
+
}
|
|
15302
|
+
for (const [cid, ts2] of historyFilterTimestamps) {
|
|
15303
|
+
if (now - ts2 > STALE_THRESHOLD_MS) {
|
|
15304
|
+
historyFilters.delete(cid);
|
|
15305
|
+
historyFilterTimestamps.delete(cid);
|
|
15306
|
+
}
|
|
15307
|
+
}
|
|
15308
|
+
for (const [cid, ts2] of councilResultTimestamps) {
|
|
15309
|
+
if (now - ts2 > STALE_THRESHOLD_MS) {
|
|
15310
|
+
councilResults.delete(cid);
|
|
15311
|
+
councilResultTimestamps.delete(cid);
|
|
15312
|
+
}
|
|
15313
|
+
}
|
|
15314
|
+
for (const [cid, ts2] of pendingModelSearchTimestamps) {
|
|
15315
|
+
if (now - ts2 > STALE_THRESHOLD_MS) {
|
|
15316
|
+
pendingModelSearch.delete(cid);
|
|
15317
|
+
pendingModelSearchTimestamps.delete(cid);
|
|
15318
|
+
}
|
|
15319
|
+
}
|
|
15320
|
+
for (const chatId of pendingInterrupts.keys()) {
|
|
15321
|
+
if (!_interruptSeen.has(chatId)) {
|
|
15322
|
+
_interruptSeen.add(chatId);
|
|
15323
|
+
} else {
|
|
15324
|
+
pendingInterrupts.delete(chatId);
|
|
15325
|
+
_interruptSeen.delete(chatId);
|
|
15326
|
+
}
|
|
15327
|
+
}
|
|
15328
|
+
for (const chatId of _interruptSeen) {
|
|
15329
|
+
if (!pendingInterrupts.has(chatId)) _interruptSeen.delete(chatId);
|
|
15330
|
+
}
|
|
15331
|
+
for (const [cid, ts2] of pendingCliTimestamps) {
|
|
15332
|
+
if (now - ts2 > STALE_THRESHOLD_MS) {
|
|
15333
|
+
pendingCliAdditions.delete(cid);
|
|
15334
|
+
pendingCliTimestamps.delete(cid);
|
|
15335
|
+
}
|
|
15336
|
+
}
|
|
15337
|
+
for (const [cid, state] of pendingMcpImports) {
|
|
15338
|
+
if (now - state.startedAt > STALE_THRESHOLD_MS) pendingMcpImports.delete(cid);
|
|
15339
|
+
}
|
|
15340
|
+
for (const [cid, ts2] of _forceStoppedTimestamps) {
|
|
15341
|
+
if (now - ts2 > STALE_THRESHOLD_MS) {
|
|
15342
|
+
_forceStoppedChats.delete(cid);
|
|
15343
|
+
_forceStoppedTimestamps.delete(cid);
|
|
15344
|
+
}
|
|
15345
|
+
}
|
|
15346
|
+
}, SWEEP_INTERVAL_MS);
|
|
15347
|
+
sweepTimer.unref();
|
|
15348
|
+
}
|
|
15349
|
+
function stopStateSweep() {
|
|
15350
|
+
if (sweepTimer) {
|
|
15351
|
+
clearInterval(sweepTimer);
|
|
15352
|
+
sweepTimer = null;
|
|
15353
|
+
}
|
|
15354
|
+
}
|
|
15355
|
+
function setPendingCliAddition(chatId, messageId) {
|
|
15356
|
+
pendingCliAdditions.set(chatId, messageId);
|
|
15357
|
+
pendingCliTimestamps.set(chatId, Date.now());
|
|
15358
|
+
}
|
|
15359
|
+
function clearPendingCliAddition(chatId) {
|
|
15360
|
+
pendingCliAdditions.delete(chatId);
|
|
15361
|
+
pendingCliTimestamps.delete(chatId);
|
|
15362
|
+
}
|
|
15363
|
+
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;
|
|
15364
|
+
var init_state = __esm({
|
|
15365
|
+
"src/router/state.ts"() {
|
|
15366
|
+
"use strict";
|
|
15367
|
+
init_agent();
|
|
15368
|
+
pendingInterrupts = /* @__PURE__ */ new Map();
|
|
15369
|
+
bypassBusyCheck = /* @__PURE__ */ new Set();
|
|
15370
|
+
_forceStoppedChats = /* @__PURE__ */ new Set();
|
|
15371
|
+
_forceStoppedTimestamps = /* @__PURE__ */ new Map();
|
|
15372
|
+
activeSideQuests = /* @__PURE__ */ new Map();
|
|
15373
|
+
dashboardClawWarnings = /* @__PURE__ */ new Map();
|
|
15374
|
+
pendingSummaryUndo = /* @__PURE__ */ new Map();
|
|
15375
|
+
pendingNewchatUndo = /* @__PURE__ */ new Map();
|
|
15376
|
+
historyFilters = /* @__PURE__ */ new Map();
|
|
15377
|
+
historyFilterTimestamps = /* @__PURE__ */ new Map();
|
|
15378
|
+
councilResults = /* @__PURE__ */ new Map();
|
|
15379
|
+
councilResultTimestamps = /* @__PURE__ */ new Map();
|
|
15380
|
+
pendingModelSearch = /* @__PURE__ */ new Map();
|
|
15381
|
+
pendingModelSearchTimestamps = /* @__PURE__ */ new Map();
|
|
15382
|
+
pendingModelResults = /* @__PURE__ */ new Map();
|
|
15383
|
+
SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
|
|
15384
|
+
STALE_THRESHOLD_MS = 30 * 60 * 1e3;
|
|
15385
|
+
sweepTimer = null;
|
|
15386
|
+
_interruptSeen = /* @__PURE__ */ new Set();
|
|
15387
|
+
pendingMcpImports = /* @__PURE__ */ new Map();
|
|
15388
|
+
pendingCliAdditions = /* @__PURE__ */ new Map();
|
|
15389
|
+
pendingCliTimestamps = /* @__PURE__ */ new Map();
|
|
15390
|
+
}
|
|
15391
|
+
});
|
|
15392
|
+
|
|
14275
15393
|
// src/ui/pagination.ts
|
|
14276
15394
|
function buildPaginatedKeyboard(opts) {
|
|
14277
15395
|
const { items, page, callbackPrefix, renderItem, headerText, footerButtons } = opts;
|
|
@@ -14897,6 +16015,13 @@ async function promptAccount(chatId, channel) {
|
|
|
14897
16015
|
await promptTimeout(chatId, channel);
|
|
14898
16016
|
return;
|
|
14899
16017
|
}
|
|
16018
|
+
const adapter = getAdapter(pending.backend);
|
|
16019
|
+
if (adapter.type === "api") {
|
|
16020
|
+
pending.credentialSlotId = null;
|
|
16021
|
+
pending.step = "timeout";
|
|
16022
|
+
await promptTimeout(chatId, channel);
|
|
16023
|
+
return;
|
|
16024
|
+
}
|
|
14900
16025
|
const isGemini = pending.backend === BACKEND.GEMINI;
|
|
14901
16026
|
const slots = isGemini ? getGeminiSlots() : getBackendSlots(pending.backend);
|
|
14902
16027
|
const enabledSlots = slots.filter((s) => s.enabled);
|
|
@@ -15158,6 +16283,7 @@ __export(stt_exports, {
|
|
|
15158
16283
|
LOCAL_WHISPER_MODELS: () => LOCAL_WHISPER_MODELS,
|
|
15159
16284
|
MACOS_VOICES: () => MACOS_VOICES,
|
|
15160
16285
|
downloadWhisperModel: () => downloadWhisperModel,
|
|
16286
|
+
getSttEcho: () => getSttEcho,
|
|
15161
16287
|
getSttModel: () => getSttModel,
|
|
15162
16288
|
getSttProvider: () => getSttProvider,
|
|
15163
16289
|
getVoiceConfig: () => getVoiceConfig,
|
|
@@ -15165,10 +16291,12 @@ __export(stt_exports, {
|
|
|
15165
16291
|
isVoiceEnabled: () => isVoiceEnabled,
|
|
15166
16292
|
isWhisperCliAvailable: () => isWhisperCliAvailable,
|
|
15167
16293
|
isWhisperModelDownloaded: () => isWhisperModelDownloaded,
|
|
16294
|
+
setSttEcho: () => setSttEcho,
|
|
15168
16295
|
setSttModel: () => setSttModel,
|
|
15169
16296
|
setSttProvider: () => setSttProvider,
|
|
15170
16297
|
setVoiceProvider: () => setVoiceProvider,
|
|
15171
16298
|
synthesizeSpeech: () => synthesizeSpeech,
|
|
16299
|
+
toggleSttEcho: () => toggleSttEcho,
|
|
15172
16300
|
toggleVoice: () => toggleVoice,
|
|
15173
16301
|
transcribeAudio: () => transcribeAudio
|
|
15174
16302
|
});
|
|
@@ -15247,6 +16375,23 @@ function setSttModel(chatId, model2) {
|
|
|
15247
16375
|
ON CONFLICT(chat_id) DO UPDATE SET stt_model = ?
|
|
15248
16376
|
`).run(chatId, model2, model2);
|
|
15249
16377
|
}
|
|
16378
|
+
function getSttEcho(chatId) {
|
|
16379
|
+
const db3 = getDb();
|
|
16380
|
+
const row = db3.prepare("SELECT stt_echo FROM chat_voice WHERE chat_id = ?").get(chatId);
|
|
16381
|
+
return row?.stt_echo === 1;
|
|
16382
|
+
}
|
|
16383
|
+
function setSttEcho(chatId, enabled) {
|
|
16384
|
+
const db3 = getDb();
|
|
16385
|
+
db3.prepare(`
|
|
16386
|
+
INSERT INTO chat_voice (chat_id, enabled, stt_echo) VALUES (?, 0, ?)
|
|
16387
|
+
ON CONFLICT(chat_id) DO UPDATE SET stt_echo = ?
|
|
16388
|
+
`).run(chatId, enabled ? 1 : 0, enabled ? 1 : 0);
|
|
16389
|
+
}
|
|
16390
|
+
function toggleSttEcho(chatId) {
|
|
16391
|
+
const newState = !getSttEcho(chatId);
|
|
16392
|
+
setSttEcho(chatId, newState);
|
|
16393
|
+
return newState;
|
|
16394
|
+
}
|
|
15250
16395
|
function isFfmpegAvailable() {
|
|
15251
16396
|
if (ffmpegAvailable !== null) return ffmpegAvailable;
|
|
15252
16397
|
try {
|
|
@@ -15624,251 +16769,6 @@ var init_health2 = __esm({
|
|
|
15624
16769
|
}
|
|
15625
16770
|
});
|
|
15626
16771
|
|
|
15627
|
-
// src/channels/telegram-throttle.ts
|
|
15628
|
-
var telegram_throttle_exports = {};
|
|
15629
|
-
__export(telegram_throttle_exports, {
|
|
15630
|
-
TelegramThrottle: () => TelegramThrottle,
|
|
15631
|
-
getThrottleState: () => getThrottleState
|
|
15632
|
-
});
|
|
15633
|
-
import { GrammyError } from "grammy";
|
|
15634
|
-
function isEditLabel(label2) {
|
|
15635
|
-
return label2.startsWith("editText") || label2.startsWith("editKeyboard");
|
|
15636
|
-
}
|
|
15637
|
-
function perChatInterval(chatId) {
|
|
15638
|
-
return parseInt(chatId) < 0 ? PER_GROUP_INTERVAL_MS : PER_DM_INTERVAL_MS;
|
|
15639
|
-
}
|
|
15640
|
-
function getThrottleState() {
|
|
15641
|
-
if (!_activeThrottle) return null;
|
|
15642
|
-
return _activeThrottle.getState();
|
|
15643
|
-
}
|
|
15644
|
-
function is429(err) {
|
|
15645
|
-
return err instanceof GrammyError && err.error_code === 429;
|
|
15646
|
-
}
|
|
15647
|
-
function sleep(ms) {
|
|
15648
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
15649
|
-
}
|
|
15650
|
-
var PER_DM_INTERVAL_MS, PER_GROUP_INTERVAL_MS, GLOBAL_INTERVAL_MS, MAX_RETRIES2, RETRY_DELAY_MS, MAX_QUEUE_SIZE, EDIT_PRESSURE_THRESHOLD, MAX_TOTAL_PAUSE_MS, _activeThrottle, TelegramThrottle;
|
|
15651
|
-
var init_telegram_throttle = __esm({
|
|
15652
|
-
"src/channels/telegram-throttle.ts"() {
|
|
15653
|
-
"use strict";
|
|
15654
|
-
init_log();
|
|
15655
|
-
PER_DM_INTERVAL_MS = 1e3;
|
|
15656
|
-
PER_GROUP_INTERVAL_MS = 3500;
|
|
15657
|
-
GLOBAL_INTERVAL_MS = 100;
|
|
15658
|
-
MAX_RETRIES2 = 2;
|
|
15659
|
-
RETRY_DELAY_MS = 1e3;
|
|
15660
|
-
MAX_QUEUE_SIZE = 100;
|
|
15661
|
-
EDIT_PRESSURE_THRESHOLD = MAX_QUEUE_SIZE / 2;
|
|
15662
|
-
MAX_TOTAL_PAUSE_MS = 30 * 60 * 1e3;
|
|
15663
|
-
_activeThrottle = null;
|
|
15664
|
-
TelegramThrottle = class {
|
|
15665
|
-
queue = [];
|
|
15666
|
-
processing = false;
|
|
15667
|
-
lastSendPerChat = /* @__PURE__ */ new Map();
|
|
15668
|
-
lastGlobalSend = 0;
|
|
15669
|
-
// Pause state
|
|
15670
|
-
pausedUntil = 0;
|
|
15671
|
-
pauseStartedAt = 0;
|
|
15672
|
-
chatsPendingNotification = /* @__PURE__ */ new Set();
|
|
15673
|
-
resumeNotifier;
|
|
15674
|
-
constructor() {
|
|
15675
|
-
_activeThrottle = this;
|
|
15676
|
-
}
|
|
15677
|
-
/**
|
|
15678
|
-
* Register a callback that fires when the throttle resumes after a pause.
|
|
15679
|
-
* The callback should send a message directly via bot.api (NOT through the throttle).
|
|
15680
|
-
*/
|
|
15681
|
-
setResumeNotifier(fn) {
|
|
15682
|
-
this.resumeNotifier = fn;
|
|
15683
|
-
}
|
|
15684
|
-
/** Enqueue a Telegram API call with automatic pacing and 429 handling.
|
|
15685
|
-
* When `priority` is true the item jumps to the front of the queue —
|
|
15686
|
-
* used by fast-path commands (/status, /stop, etc.) so their responses
|
|
15687
|
-
* aren't delayed behind tool-notification or error-message floods. */
|
|
15688
|
-
async send(chatId, label2, fn, priority) {
|
|
15689
|
-
if (isEditLabel(label2)) {
|
|
15690
|
-
if (this.isPaused()) {
|
|
15691
|
-
throw new Error("Throttle paused (rate limit active) \u2014 edit skipped");
|
|
15692
|
-
}
|
|
15693
|
-
if (this.queue.length >= EDIT_PRESSURE_THRESHOLD) {
|
|
15694
|
-
throw new Error("Throttle queue pressured \u2014 edit skipped");
|
|
15695
|
-
}
|
|
15696
|
-
}
|
|
15697
|
-
return new Promise((resolve3, reject) => {
|
|
15698
|
-
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
15699
|
-
const editIdx = this.queue.findIndex((q) => isEditLabel(q.label));
|
|
15700
|
-
const dropIdx = editIdx >= 0 ? editIdx : 0;
|
|
15701
|
-
const dropped = this.queue.splice(dropIdx, 1)[0];
|
|
15702
|
-
if (dropped) {
|
|
15703
|
-
warn(`[throttle] Queue full (${MAX_QUEUE_SIZE}), dropping: ${dropped.label}`);
|
|
15704
|
-
dropped.reject(new Error("Dropped from send queue (overflow)"));
|
|
15705
|
-
}
|
|
15706
|
-
}
|
|
15707
|
-
const item = { chatId, label: label2, fn, resolve: resolve3, reject };
|
|
15708
|
-
if (priority) {
|
|
15709
|
-
this.queue.unshift(item);
|
|
15710
|
-
} else {
|
|
15711
|
-
this.queue.push(item);
|
|
15712
|
-
}
|
|
15713
|
-
this.drain();
|
|
15714
|
-
});
|
|
15715
|
-
}
|
|
15716
|
-
/**
|
|
15717
|
-
* Best-effort send — drops silently if throttle is paused or queue is pressured.
|
|
15718
|
-
* Used for cosmetic calls (typing indicators, reactions) that should count toward
|
|
15719
|
-
* rate limits but must never queue up or amplify 429 spirals.
|
|
15720
|
-
*/
|
|
15721
|
-
async tryBestEffort(chatId, label2, fn, opts) {
|
|
15722
|
-
if (this.isPaused()) return void 0;
|
|
15723
|
-
if (this.queue.length > 10) return void 0;
|
|
15724
|
-
if (!opts?.skipRecord) {
|
|
15725
|
-
const lastChat = this.lastSendPerChat.get(chatId) ?? 0;
|
|
15726
|
-
if (Date.now() - lastChat < perChatInterval(chatId)) return void 0;
|
|
15727
|
-
if (Date.now() - this.lastGlobalSend < GLOBAL_INTERVAL_MS) return void 0;
|
|
15728
|
-
}
|
|
15729
|
-
try {
|
|
15730
|
-
const result = await fn();
|
|
15731
|
-
if (!opts?.skipRecord) this.recordSend(chatId);
|
|
15732
|
-
return result;
|
|
15733
|
-
} catch (err) {
|
|
15734
|
-
if (is429(err)) {
|
|
15735
|
-
const retrySec = err.parameters?.retry_after ?? 10;
|
|
15736
|
-
this.pausedUntil = Date.now() + retrySec * 1e3;
|
|
15737
|
-
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
15738
|
-
warn(`[throttle] Best-effort ${label2} hit 429, pausing for ${retrySec}s`);
|
|
15739
|
-
}
|
|
15740
|
-
return void 0;
|
|
15741
|
-
}
|
|
15742
|
-
}
|
|
15743
|
-
/** Check whether the throttle is currently paused (rate-limited). */
|
|
15744
|
-
isPaused() {
|
|
15745
|
-
return Date.now() < this.pausedUntil;
|
|
15746
|
-
}
|
|
15747
|
-
/** Get structured state for diagnostics / health checks. */
|
|
15748
|
-
getState() {
|
|
15749
|
-
const now = Date.now();
|
|
15750
|
-
const paused = now < this.pausedUntil;
|
|
15751
|
-
return {
|
|
15752
|
-
isPaused: paused,
|
|
15753
|
-
queueDepth: this.queue.length,
|
|
15754
|
-
pausedUntilMs: this.pausedUntil,
|
|
15755
|
-
pauseRemainingSec: paused ? Math.ceil((this.pausedUntil - now) / 1e3) : 0
|
|
15756
|
-
};
|
|
15757
|
-
}
|
|
15758
|
-
// ── Queue processor ─────────────────────────────────────────────────
|
|
15759
|
-
async drain() {
|
|
15760
|
-
if (this.processing) return;
|
|
15761
|
-
this.processing = true;
|
|
15762
|
-
try {
|
|
15763
|
-
while (this.queue.length > 0) {
|
|
15764
|
-
while (this.isPaused()) {
|
|
15765
|
-
if (this.pauseStartedAt > 0 && Date.now() - this.pauseStartedAt > MAX_TOTAL_PAUSE_MS) {
|
|
15766
|
-
warn(`[throttle] Max pause duration exceeded (${MAX_TOTAL_PAUSE_MS / 6e4}min), dropping ${this.queue.length} items`);
|
|
15767
|
-
this.flushQueueWithError("Telegram rate limit exceeded max wait time");
|
|
15768
|
-
this.pausedUntil = 0;
|
|
15769
|
-
this.pauseStartedAt = 0;
|
|
15770
|
-
this.chatsPendingNotification.clear();
|
|
15771
|
-
break;
|
|
15772
|
-
}
|
|
15773
|
-
const waitMs = Math.min(this.pausedUntil - Date.now(), 5e3);
|
|
15774
|
-
if (waitMs > 0) await sleep(waitMs);
|
|
15775
|
-
}
|
|
15776
|
-
if (this.queue.length === 0) break;
|
|
15777
|
-
if (this.chatsPendingNotification.size > 0) {
|
|
15778
|
-
await this.sendResumeNotifications();
|
|
15779
|
-
}
|
|
15780
|
-
const item = this.queue[0];
|
|
15781
|
-
const lastChat = this.lastSendPerChat.get(item.chatId) ?? 0;
|
|
15782
|
-
const chatWait = perChatInterval(item.chatId) - (Date.now() - lastChat);
|
|
15783
|
-
if (chatWait > 0) await sleep(chatWait);
|
|
15784
|
-
const globalWait = GLOBAL_INTERVAL_MS - (Date.now() - this.lastGlobalSend);
|
|
15785
|
-
if (globalWait > 0) await sleep(globalWait);
|
|
15786
|
-
this.queue.shift();
|
|
15787
|
-
try {
|
|
15788
|
-
const result = await this.execWithRetry(item.label, item.fn);
|
|
15789
|
-
this.recordSend(item.chatId);
|
|
15790
|
-
this.pauseStartedAt = 0;
|
|
15791
|
-
item.resolve(result);
|
|
15792
|
-
} catch (err) {
|
|
15793
|
-
if (is429(err)) {
|
|
15794
|
-
const retrySec = err.parameters?.retry_after ?? 10;
|
|
15795
|
-
this.enterPause(retrySec, item);
|
|
15796
|
-
continue;
|
|
15797
|
-
}
|
|
15798
|
-
item.reject(err);
|
|
15799
|
-
}
|
|
15800
|
-
}
|
|
15801
|
-
} finally {
|
|
15802
|
-
this.processing = false;
|
|
15803
|
-
}
|
|
15804
|
-
}
|
|
15805
|
-
// ── Retry logic (non-429 errors only) ───────────────────────────────
|
|
15806
|
-
async execWithRetry(label2, fn) {
|
|
15807
|
-
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
15808
|
-
try {
|
|
15809
|
-
return await fn();
|
|
15810
|
-
} catch (err) {
|
|
15811
|
-
if (is429(err)) throw err;
|
|
15812
|
-
if (attempt < MAX_RETRIES2 && err instanceof GrammyError) {
|
|
15813
|
-
warn(`[throttle] ${label2} attempt ${attempt + 1}/${MAX_RETRIES2} failed (${err.error_code}), retrying`);
|
|
15814
|
-
await sleep(RETRY_DELAY_MS);
|
|
15815
|
-
continue;
|
|
15816
|
-
}
|
|
15817
|
-
throw err;
|
|
15818
|
-
}
|
|
15819
|
-
}
|
|
15820
|
-
throw new Error("unreachable");
|
|
15821
|
-
}
|
|
15822
|
-
// ── Pause management ────────────────────────────────────────────────
|
|
15823
|
-
enterPause(retrySec, failedItem) {
|
|
15824
|
-
this.queue.unshift(failedItem);
|
|
15825
|
-
const bufferedSec = Math.ceil(retrySec * 1.5);
|
|
15826
|
-
this.pausedUntil = Date.now() + bufferedSec * 1e3;
|
|
15827
|
-
if (this.pauseStartedAt === 0) this.pauseStartedAt = Date.now();
|
|
15828
|
-
for (const qi of this.queue) {
|
|
15829
|
-
this.chatsPendingNotification.add(qi.chatId);
|
|
15830
|
-
}
|
|
15831
|
-
warn(`[throttle] 429 \u2014 pausing ALL sends for ${bufferedSec}s (retry_after=${retrySec}s + 50% buffer, ${this.queue.length} items queued)`);
|
|
15832
|
-
}
|
|
15833
|
-
async sendResumeNotifications() {
|
|
15834
|
-
const chats2 = new Set(this.chatsPendingNotification);
|
|
15835
|
-
this.chatsPendingNotification.clear();
|
|
15836
|
-
if (!this.resumeNotifier) return;
|
|
15837
|
-
const pausedSec = this.pauseStartedAt > 0 ? Math.round((Date.now() - this.pauseStartedAt) / 1e3) : 0;
|
|
15838
|
-
for (const chatId of chats2) {
|
|
15839
|
-
const queuedForChat = this.queue.filter((q) => q.chatId === chatId).length;
|
|
15840
|
-
if (queuedForChat === 0) continue;
|
|
15841
|
-
try {
|
|
15842
|
-
await this.resumeNotifier(chatId, pausedSec, queuedForChat);
|
|
15843
|
-
this.recordSend(chatId);
|
|
15844
|
-
} catch (err) {
|
|
15845
|
-
if (is429(err)) {
|
|
15846
|
-
const retrySec = err.parameters?.retry_after ?? 10;
|
|
15847
|
-
this.pausedUntil = Date.now() + retrySec * 1e3;
|
|
15848
|
-
warn(`[throttle] Resume notification hit 429, re-pausing for ${retrySec}s (skipping further notifications)`);
|
|
15849
|
-
return;
|
|
15850
|
-
}
|
|
15851
|
-
warn(`[throttle] Resume notification failed for chat ${chatId}: ${err}`);
|
|
15852
|
-
}
|
|
15853
|
-
}
|
|
15854
|
-
this.pauseStartedAt = 0;
|
|
15855
|
-
}
|
|
15856
|
-
// ── Helpers ─────────────────────────────────────────────────────────
|
|
15857
|
-
recordSend(chatId) {
|
|
15858
|
-
const now = Date.now();
|
|
15859
|
-
this.lastSendPerChat.set(chatId, now);
|
|
15860
|
-
this.lastGlobalSend = now;
|
|
15861
|
-
}
|
|
15862
|
-
flushQueueWithError(message) {
|
|
15863
|
-
while (this.queue.length > 0) {
|
|
15864
|
-
const item = this.queue.shift();
|
|
15865
|
-
item.reject(new Error(message));
|
|
15866
|
-
}
|
|
15867
|
-
}
|
|
15868
|
-
};
|
|
15869
|
-
}
|
|
15870
|
-
});
|
|
15871
|
-
|
|
15872
16772
|
// src/health/checks.ts
|
|
15873
16773
|
import { existsSync as existsSync16, statSync as statSync5, readFileSync as readFileSync11 } from "fs";
|
|
15874
16774
|
import { execFileSync as execFileSync2, execSync as execSync2 } from "child_process";
|
|
@@ -16135,8 +17035,8 @@ function enableHeartbeat(chatId, opts) {
|
|
|
16135
17035
|
const { insertJob: insertJob2 } = (init_jobs(), __toCommonJS(jobs_exports));
|
|
16136
17036
|
const { startSingleJob: startSingleJob2 } = (init_cron(), __toCommonJS(cron_exports));
|
|
16137
17037
|
const intervalMs = opts?.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
16138
|
-
const backend2 = opts?.backend ??
|
|
16139
|
-
const model2 = opts?.model ??
|
|
17038
|
+
const backend2 = opts?.backend ?? resolveDefaultBackend2(chatId);
|
|
17039
|
+
const model2 = opts?.model ?? resolveDefaultModel2(backend2);
|
|
16140
17040
|
const job = insertJob2({
|
|
16141
17041
|
scheduleType: "every",
|
|
16142
17042
|
everyMs: intervalMs,
|
|
@@ -16156,7 +17056,7 @@ function enableHeartbeat(chatId, opts) {
|
|
|
16156
17056
|
log(`[heartbeat] Created job #${job.id} (every ${intervalMs / 6e4}min, backend=${backend2}, model=${model2})`);
|
|
16157
17057
|
return job.id;
|
|
16158
17058
|
}
|
|
16159
|
-
function
|
|
17059
|
+
function resolveDefaultBackend2(chatId) {
|
|
16160
17060
|
try {
|
|
16161
17061
|
const { getAdapterForChat: getAdapterForChat2 } = (init_backends(), __toCommonJS(backends_exports));
|
|
16162
17062
|
return getAdapterForChat2(chatId).id;
|
|
@@ -16164,7 +17064,7 @@ function resolveDefaultBackend(chatId) {
|
|
|
16164
17064
|
return "claude";
|
|
16165
17065
|
}
|
|
16166
17066
|
}
|
|
16167
|
-
function
|
|
17067
|
+
function resolveDefaultModel2(backend2) {
|
|
16168
17068
|
try {
|
|
16169
17069
|
const { getAdapter: getAdapter3 } = (init_backends(), __toCommonJS(backends_exports));
|
|
16170
17070
|
return getAdapter3(backend2).defaultModel;
|
|
@@ -16173,8 +17073,8 @@ function resolveDefaultModel(backend2) {
|
|
|
16173
17073
|
}
|
|
16174
17074
|
}
|
|
16175
17075
|
function migrateHeartbeatDefaults(job) {
|
|
16176
|
-
const backend2 =
|
|
16177
|
-
const model2 =
|
|
17076
|
+
const backend2 = resolveDefaultBackend2(job.chatId);
|
|
17077
|
+
const model2 = resolveDefaultModel2(backend2);
|
|
16178
17078
|
try {
|
|
16179
17079
|
const { getDb: getDb2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
16180
17080
|
getDb2().prepare("UPDATE jobs SET backend = ?, model = ? WHERE id = ?").run(backend2, model2, job.id);
|
|
@@ -16418,10 +17318,10 @@ function formatNightlySummary(insights, totalPending) {
|
|
|
16418
17318
|
} else {
|
|
16419
17319
|
header2 = `Nightly Reflection \u2014 ${newCount} proposal${newCount === 1 ? "" : "s"} ready`;
|
|
16420
17320
|
}
|
|
16421
|
-
const
|
|
17321
|
+
const list2 = insights.map((ins, i) => `\u2022 [${ins.category}] ${ins.insight}`).join("\n");
|
|
16422
17322
|
return `${header2}
|
|
16423
17323
|
|
|
16424
|
-
${
|
|
17324
|
+
${list2}
|
|
16425
17325
|
|
|
16426
17326
|
Review with /evolve`;
|
|
16427
17327
|
}
|
|
@@ -17038,6 +17938,12 @@ async function sendVoiceConfigKeyboard(chatId, channel, messageId) {
|
|
|
17038
17938
|
buttons.push(row);
|
|
17039
17939
|
}
|
|
17040
17940
|
}
|
|
17941
|
+
const echoOn = getSttEcho(chatId);
|
|
17942
|
+
buttons.push([{
|
|
17943
|
+
label: `${echoOn ? "\u2713 " : ""}\u{1F399} Show Transcription`,
|
|
17944
|
+
data: "vcfg:echo",
|
|
17945
|
+
...echoOn ? { style: "success" } : {}
|
|
17946
|
+
}]);
|
|
17041
17947
|
buttons.push([
|
|
17042
17948
|
{
|
|
17043
17949
|
label: `${!ttsConfig.enabled ? "\u2713 " : ""}\u{1F507} Replies Off`,
|
|
@@ -17204,38 +18110,32 @@ async function sendThinkingKeyboard(chatId, channel, messageId, forModelId) {
|
|
|
17204
18110
|
const currentModel = forModelId ?? getModel(chatId) ?? adapter.defaultModel;
|
|
17205
18111
|
const modelInfo = adapter.availableModels[currentModel];
|
|
17206
18112
|
const currentLevel = getThinkingLevel(chatId) || "auto";
|
|
17207
|
-
if (!modelInfo || modelInfo.thinking !== "adjustable" || !modelInfo.thinkingLevels) {
|
|
17208
|
-
await sendOrEditKeyboard(
|
|
17209
|
-
chatId,
|
|
17210
|
-
channel,
|
|
17211
|
-
messageId,
|
|
17212
|
-
`Model ${shortModelName(currentModel)} uses fixed thinking \u2014 no adjustment needed.`,
|
|
17213
|
-
[[{ label: "\u2190 Back to Model", data: "menu:model" }]]
|
|
17214
|
-
);
|
|
17215
|
-
return;
|
|
17216
|
-
}
|
|
17217
18113
|
const showThinkingUi = getShowThinkingUi(chatId);
|
|
17218
|
-
const
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
18114
|
+
const canAdjust = modelInfo?.thinking === "adjustable" && modelInfo.thinkingLevels;
|
|
18115
|
+
const buttons = [];
|
|
18116
|
+
if (canAdjust) {
|
|
18117
|
+
for (const level of modelInfo.thinkingLevels) {
|
|
18118
|
+
buttons.push([{
|
|
18119
|
+
label: `${level === currentLevel ? "\u2713 " : ""}${level === "auto" ? "Auto" : capitalize(level)}`,
|
|
18120
|
+
data: `thinking:${level}`,
|
|
18121
|
+
...level === currentLevel ? { style: "primary" } : {}
|
|
18122
|
+
}]);
|
|
18123
|
+
}
|
|
18124
|
+
}
|
|
17223
18125
|
buttons.push([{
|
|
17224
18126
|
label: `${showThinkingUi ? "\u2713 " : ""}\u{1F4AD} Show Thinking`,
|
|
17225
18127
|
data: "thinking_show_ui:toggle",
|
|
17226
18128
|
...showThinkingUi ? { style: "primary" } : {}
|
|
17227
18129
|
}]);
|
|
17228
|
-
|
|
17229
|
-
chatId,
|
|
17230
|
-
channel,
|
|
17231
|
-
messageId,
|
|
17232
|
-
`\u{1F4AD} Thinking Level \u2014 ${shortModelName(currentModel)}
|
|
18130
|
+
const header2 = canAdjust ? `\u{1F4AD} Thinking Level \u2014 ${shortModelName(currentModel)}
|
|
17233
18131
|
Current: ${capitalize(currentLevel)}
|
|
17234
|
-
Show thinking tokens: ${showThinkingUi ? "On" : "Off"}
|
|
18132
|
+
Show thinking tokens: ${showThinkingUi ? "On" : "Off"}` : `\u{1F4AD} Thinking \u2014 ${shortModelName(currentModel)}
|
|
18133
|
+
Level: Fixed
|
|
18134
|
+
Show thinking tokens: ${showThinkingUi ? "On" : "Off"}`;
|
|
18135
|
+
const note = adapter.id === "cursor" ? `
|
|
17235
18136
|
|
|
17236
|
-
\u26A0\uFE0F ${adapter.displayName} doesn't expose thinking tokens` : ""
|
|
17237
|
-
|
|
17238
|
-
);
|
|
18137
|
+
\u26A0\uFE0F ${adapter.displayName} doesn't expose thinking tokens` : "";
|
|
18138
|
+
await sendOrEditKeyboard(chatId, channel, messageId, `${header2}${note}`, buttons);
|
|
17239
18139
|
}
|
|
17240
18140
|
async function sendSkillsPage(chatId, channel, skills2, page, messageId) {
|
|
17241
18141
|
const approved = skills2.filter((s) => s.status === "approved");
|
|
@@ -17330,9 +18230,11 @@ async function sendMemoryPage(chatId, channel, page, messageId) {
|
|
|
17330
18230
|
const buttons = [];
|
|
17331
18231
|
for (const [i, m] of pageItems.entries()) {
|
|
17332
18232
|
const num = start + i + 1;
|
|
17333
|
-
const
|
|
18233
|
+
const cat = m.category ?? "uncategorized";
|
|
18234
|
+
const full = `[${cat}] ${m.trigger}: ${m.content}`;
|
|
18235
|
+
const preview = full.length > 36 ? `${full.slice(0, 36)}\u2026` : full;
|
|
17334
18236
|
buttons.push([{
|
|
17335
|
-
label: `${num}. ${preview}
|
|
18237
|
+
label: `${num}. ${preview}`,
|
|
17336
18238
|
data: `mem:view:${m.id}`
|
|
17337
18239
|
}]);
|
|
17338
18240
|
}
|
|
@@ -17351,6 +18253,12 @@ async function sendMemoryPage(chatId, channel, page, messageId) {
|
|
|
17351
18253
|
}
|
|
17352
18254
|
buttons.push(footerRow);
|
|
17353
18255
|
buttons.push([{ label: "\u2728 Optimize", data: "mem:opt", style: "success" }]);
|
|
18256
|
+
const sweepEnabled = getMetaValue(SWEEP_ENABLED_KEY) === "1";
|
|
18257
|
+
buttons.push([{
|
|
18258
|
+
label: sweepEnabled ? "\u2713 Weekly Sweep: On" : "Weekly Sweep: Off",
|
|
18259
|
+
data: "mem:sweep:toggle",
|
|
18260
|
+
style: sweepEnabled ? "success" : void 0
|
|
18261
|
+
}]);
|
|
17354
18262
|
await sendOrEditKeyboard(chatId, channel, messageId, header2, buttons);
|
|
17355
18263
|
}
|
|
17356
18264
|
async function sendMemoryDetail(chatId, memoryId, channel, messageId) {
|
|
@@ -18028,7 +18936,8 @@ async function sendBackendModelPicker(chatId, backendId, channel, messageId) {
|
|
|
18028
18936
|
const summary = backendConfigSummary(chatId, backendId, false);
|
|
18029
18937
|
if (adapter.type === "api") {
|
|
18030
18938
|
const apiModels = getApiModels(backendId);
|
|
18031
|
-
|
|
18939
|
+
const adapterModelCount = Object.keys(adapter.availableModels).length;
|
|
18940
|
+
if (apiModels.length === 0 && adapterModelCount === 0) {
|
|
18032
18941
|
await sendOrEditKeyboard(
|
|
18033
18942
|
chatId,
|
|
18034
18943
|
channel,
|
|
@@ -18043,23 +18952,25 @@ No models configured. Add one with \u2795`,
|
|
|
18043
18952
|
);
|
|
18044
18953
|
return;
|
|
18045
18954
|
}
|
|
18046
|
-
|
|
18047
|
-
|
|
18048
|
-
const
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
18056
|
-
|
|
18057
|
-
|
|
18955
|
+
if (apiModels.length > 0) {
|
|
18956
|
+
const modelButtons2 = [];
|
|
18957
|
+
for (const m of apiModels) {
|
|
18958
|
+
const isActive = m.modelId === currentModel;
|
|
18959
|
+
const freeTag = m.isFree && !m.displayName.toLowerCase().includes("free") ? " (free)" : "";
|
|
18960
|
+
modelButtons2.push([
|
|
18961
|
+
{
|
|
18962
|
+
label: `${isActive ? "\u2713 " : ""}${m.displayName}${freeTag}`,
|
|
18963
|
+
data: `apimodel:sel:${m.id}`,
|
|
18964
|
+
...isActive ? { style: "primary" } : {}
|
|
18965
|
+
},
|
|
18966
|
+
{ label: "\u{1F5D1}", data: `apimodel:del:${m.id}` }
|
|
18967
|
+
]);
|
|
18968
|
+
}
|
|
18969
|
+
modelButtons2.push([{ label: "\u2795 Add Model", data: `apimodel:add:${backendId}` }]);
|
|
18970
|
+
modelButtons2.push([{ label: "\u2190 Back", data: `bconf:panel:${backendId}` }]);
|
|
18971
|
+
await sendOrEditKeyboard(chatId, channel, messageId, summary, modelButtons2);
|
|
18972
|
+
return;
|
|
18058
18973
|
}
|
|
18059
|
-
modelButtons2.push([{ label: "\u2795 Add Model", data: `apimodel:add:${backendId}` }]);
|
|
18060
|
-
modelButtons2.push([{ label: "\u2190 Back", data: `bconf:panel:${backendId}` }]);
|
|
18061
|
-
await sendOrEditKeyboard(chatId, channel, messageId, summary, modelButtons2);
|
|
18062
|
-
return;
|
|
18063
18974
|
}
|
|
18064
18975
|
const modelButtons = Object.entries(adapter.availableModels).map(([id, info]) => [{
|
|
18065
18976
|
label: `${id === currentModel ? "\u2713 " : ""}${info.label}`,
|
|
@@ -18155,7 +19066,8 @@ async function sendBackendSwitchConfirmation(chatId, target, channel, messageId)
|
|
|
18155
19066
|
}
|
|
18156
19067
|
async function doBackendSwitch(chatId, backendId, channel, opts) {
|
|
18157
19068
|
if (!opts?.skipContext) {
|
|
18158
|
-
const
|
|
19069
|
+
const interrupted = consumeForceStoppedChat(chatId);
|
|
19070
|
+
const bridge = buildContextBridge(chatId, 15, interrupted);
|
|
18159
19071
|
if (bridge) setPendingContextBridge(chatId, bridge);
|
|
18160
19072
|
}
|
|
18161
19073
|
clearSession(chatId);
|
|
@@ -18235,10 +19147,12 @@ var init_ui = __esm({
|
|
|
18235
19147
|
init_format();
|
|
18236
19148
|
init_backends();
|
|
18237
19149
|
init_store5();
|
|
19150
|
+
init_sweep();
|
|
18238
19151
|
init_chat_settings();
|
|
18239
19152
|
init_api_models();
|
|
18240
19153
|
init_summarize();
|
|
18241
19154
|
init_inject();
|
|
19155
|
+
init_state();
|
|
18242
19156
|
init_session_log();
|
|
18243
19157
|
init_store3();
|
|
18244
19158
|
init_log();
|
|
@@ -18282,7 +19196,7 @@ function getMemOptSession(chatId) {
|
|
|
18282
19196
|
function buildOptimizationPrompt(memories) {
|
|
18283
19197
|
const lines = memories.map((m, i) => {
|
|
18284
19198
|
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}`;
|
|
19199
|
+
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
19200
|
});
|
|
18287
19201
|
return ANALYSIS_PROMPT.replace("{MEMORIES}", lines.join("\n"));
|
|
18288
19202
|
}
|
|
@@ -18291,7 +19205,7 @@ function parseOptimizationResponse(raw) {
|
|
|
18291
19205
|
const suggestions = [];
|
|
18292
19206
|
const blocks = raw.split(/^---$/m).filter((b) => b.trim());
|
|
18293
19207
|
for (const block of blocks) {
|
|
18294
|
-
const typeMatch = block.match(/TYPE:\s*(duplicate|merge|stale)/i);
|
|
19208
|
+
const typeMatch = block.match(/TYPE:\s*(duplicate|merge|stale|superseded|categorize|reclassify)/i);
|
|
18295
19209
|
const idsMatch = block.match(/IDS:\s*([\d,\s]+)/);
|
|
18296
19210
|
const reasonMatch = block.match(/REASON:\s*(.+?)(?=\n(?:ACTION|SUGGESTION|TYPE|IDS):|$)/s);
|
|
18297
19211
|
const actionMatch = block.match(/ACTION:\s*(.+)/s);
|
|
@@ -18316,6 +19230,21 @@ function parseOptimizationResponse(raw) {
|
|
|
18316
19230
|
suggestion.mergedContent = mergeMatch[2].trim();
|
|
18317
19231
|
}
|
|
18318
19232
|
}
|
|
19233
|
+
if (type === "superseded") {
|
|
19234
|
+
const supersededMatch = actionRaw.match(/supersede\s+(\d+)\s+by\s+(\d+)/i);
|
|
19235
|
+
if (supersededMatch) {
|
|
19236
|
+
suggestion.memoryIds = [
|
|
19237
|
+
parseInt(supersededMatch[1], 10),
|
|
19238
|
+
parseInt(supersededMatch[2], 10)
|
|
19239
|
+
].filter((n) => !isNaN(n));
|
|
19240
|
+
}
|
|
19241
|
+
}
|
|
19242
|
+
if (type === "categorize" || type === "reclassify") {
|
|
19243
|
+
const catMatch = actionRaw.match(/(?:categorize|reclassify)\s+\d+\s+as\s+(fact|preference|event|decision)/i);
|
|
19244
|
+
if (catMatch) {
|
|
19245
|
+
suggestion.proposedCategory = catMatch[1].toLowerCase();
|
|
19246
|
+
}
|
|
19247
|
+
}
|
|
18319
19248
|
suggestions.push(suggestion);
|
|
18320
19249
|
}
|
|
18321
19250
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -18365,14 +19294,18 @@ function applySuggestion(suggestion, session2) {
|
|
|
18365
19294
|
case "merge": {
|
|
18366
19295
|
if (!suggestion.mergedTrigger || !suggestion.mergedContent) return;
|
|
18367
19296
|
const sources = suggestion.memoryIds.map((id) => getMemoryById(id)).filter((m) => m !== void 0);
|
|
18368
|
-
const
|
|
19297
|
+
const topSource = sources.length > 0 ? sources.sort((a, b) => b.salience - a.salience)[0] : null;
|
|
19298
|
+
const sourceType = topSource?.type ?? "semantic";
|
|
19299
|
+
const sourceCategory = topSource?.category;
|
|
18369
19300
|
for (const id of suggestion.memoryIds) {
|
|
18370
19301
|
deleteMemoryById(id);
|
|
18371
19302
|
}
|
|
18372
19303
|
const newId = saveMemoryWithEmbedding(
|
|
18373
19304
|
suggestion.mergedTrigger,
|
|
18374
19305
|
suggestion.mergedContent,
|
|
18375
|
-
sourceType
|
|
19306
|
+
sourceType,
|
|
19307
|
+
sourceCategory,
|
|
19308
|
+
14
|
|
18376
19309
|
);
|
|
18377
19310
|
session2.createdIds.push(newId);
|
|
18378
19311
|
break;
|
|
@@ -18383,6 +19316,19 @@ function applySuggestion(suggestion, session2) {
|
|
|
18383
19316
|
}
|
|
18384
19317
|
break;
|
|
18385
19318
|
}
|
|
19319
|
+
case "superseded": {
|
|
19320
|
+
if (suggestion.memoryIds.length >= 2) {
|
|
19321
|
+
markSuperseded(suggestion.memoryIds[0], suggestion.memoryIds[1]);
|
|
19322
|
+
}
|
|
19323
|
+
break;
|
|
19324
|
+
}
|
|
19325
|
+
case "categorize":
|
|
19326
|
+
case "reclassify": {
|
|
19327
|
+
if (suggestion.proposedCategory && suggestion.memoryIds.length >= 1) {
|
|
19328
|
+
updateMemoryCategory(suggestion.memoryIds[0], suggestion.proposedCategory);
|
|
19329
|
+
}
|
|
19330
|
+
break;
|
|
19331
|
+
}
|
|
18386
19332
|
}
|
|
18387
19333
|
}
|
|
18388
19334
|
function resolveAnalysisAdapter(chatId, backendId, model2) {
|
|
@@ -18798,12 +19744,19 @@ Analyze these memories and find optimization opportunities:
|
|
|
18798
19744
|
|
|
18799
19745
|
1. DUPLICATES: Memories containing essentially the same information (different wording, same meaning). When found, recommend keeping the most complete version.
|
|
18800
19746
|
2. MERGEABLE: Related memories that would be stronger and more token-efficient as a single combined entry.
|
|
18801
|
-
3. STALE: Memories with very low
|
|
19747
|
+
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.
|
|
19748
|
+
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.
|
|
19749
|
+
5. UNCATEGORIZED: Memories with category "uncategorized" that can be classified as fact, preference, event, or decision.
|
|
19750
|
+
6. RECLASSIFY: Memories whose category appears wrong given their content.
|
|
19751
|
+
|
|
19752
|
+
For superseded: ACTION should be "supersede ID1 by ID2" (ID1=older memory, ID2=newer memory)
|
|
19753
|
+
For uncategorized: ACTION should be "categorize ID1 as <category>"
|
|
19754
|
+
For reclassify: ACTION should be "reclassify ID1 as <category>"
|
|
18802
19755
|
|
|
18803
19756
|
For each suggestion, output EXACTLY this format:
|
|
18804
19757
|
---
|
|
18805
19758
|
SUGGESTION: <short descriptive title>
|
|
18806
|
-
TYPE: <duplicate|merge|stale>
|
|
19759
|
+
TYPE: <duplicate|merge|stale|superseded|categorize|reclassify>
|
|
18807
19760
|
IDS: <comma-separated memory IDs to act on>
|
|
18808
19761
|
REASON: <1-2 sentence explanation>
|
|
18809
19762
|
ACTION: <"delete ID1" or "delete ID1, ID2" or "merge into: <new trigger> | <new content>">
|
|
@@ -18823,12 +19776,18 @@ Rules:
|
|
|
18823
19776
|
TYPE_EMOJI = {
|
|
18824
19777
|
duplicate: "\u{1F501}",
|
|
18825
19778
|
merge: "\u{1F500}",
|
|
18826
|
-
stale: "\u{1F5D1}"
|
|
19779
|
+
stale: "\u{1F5D1}",
|
|
19780
|
+
superseded: "\u{1F504}",
|
|
19781
|
+
categorize: "\u{1F3F7}",
|
|
19782
|
+
reclassify: "\u{1F3F7}"
|
|
18827
19783
|
};
|
|
18828
19784
|
TYPE_LABEL = {
|
|
18829
19785
|
duplicate: "Duplicate",
|
|
18830
19786
|
merge: "Merge",
|
|
18831
|
-
stale: "Stale"
|
|
19787
|
+
stale: "Stale",
|
|
19788
|
+
superseded: "Superseded",
|
|
19789
|
+
categorize: "Categorize",
|
|
19790
|
+
reclassify: "Reclassify"
|
|
18832
19791
|
};
|
|
18833
19792
|
}
|
|
18834
19793
|
});
|
|
@@ -18844,9 +19803,12 @@ var init_memory = __esm({
|
|
|
18844
19803
|
try {
|
|
18845
19804
|
const body = JSON.parse(await readBody(req));
|
|
18846
19805
|
validateAgentIdentity(req, body);
|
|
18847
|
-
const {
|
|
18848
|
-
const
|
|
18849
|
-
|
|
19806
|
+
const { remember: remember2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
19807
|
+
const result = await remember2(body.trigger ?? body.tag, body.content, {
|
|
19808
|
+
category: body.category,
|
|
19809
|
+
type: body.type
|
|
19810
|
+
});
|
|
19811
|
+
jsonResponse(res, { success: true, id: result.id });
|
|
18850
19812
|
} catch (err) {
|
|
18851
19813
|
jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
18852
19814
|
}
|
|
@@ -18855,8 +19817,8 @@ var init_memory = __esm({
|
|
|
18855
19817
|
try {
|
|
18856
19818
|
const body = JSON.parse(await readBody(req));
|
|
18857
19819
|
validateAgentIdentity(req, body);
|
|
18858
|
-
const {
|
|
18859
|
-
const results =
|
|
19820
|
+
const { recall: recall2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
19821
|
+
const results = recall2(body.query, { limit: body.limit ?? 5 });
|
|
18860
19822
|
jsonResponse(res, { success: true, results });
|
|
18861
19823
|
} catch (err) {
|
|
18862
19824
|
jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
@@ -18865,8 +19827,8 @@ var init_memory = __esm({
|
|
|
18865
19827
|
handleMemoryList = async (_req, res, url) => {
|
|
18866
19828
|
try {
|
|
18867
19829
|
const limit = parseInt(url.searchParams.get("limit") ?? "10", 10);
|
|
18868
|
-
const {
|
|
18869
|
-
const results =
|
|
19830
|
+
const { list: list2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
19831
|
+
const results = list2({ limit });
|
|
18870
19832
|
jsonResponse(res, { success: true, results });
|
|
18871
19833
|
} catch (err) {
|
|
18872
19834
|
jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
@@ -18876,14 +19838,13 @@ var init_memory = __esm({
|
|
|
18876
19838
|
try {
|
|
18877
19839
|
const body = JSON.parse(await readBody(req));
|
|
18878
19840
|
validateAgentIdentity(req, body);
|
|
19841
|
+
const { forget: forget2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
18879
19842
|
if (body.memoryId) {
|
|
18880
|
-
const
|
|
18881
|
-
|
|
18882
|
-
return jsonResponse(res, { success: deleted, mode: "id" });
|
|
19843
|
+
const count = forget2(body.memoryId);
|
|
19844
|
+
return jsonResponse(res, { success: count > 0, mode: "id" });
|
|
18883
19845
|
}
|
|
18884
19846
|
if (body.keyword) {
|
|
18885
|
-
const
|
|
18886
|
-
const count = forgetMemory4(body.keyword);
|
|
19847
|
+
const count = forget2(body.keyword);
|
|
18887
19848
|
return jsonResponse(res, { success: true, count, mode: "keyword" });
|
|
18888
19849
|
}
|
|
18889
19850
|
jsonResponse(res, { error: "Either 'keyword' or 'memoryId' is required" }, 400);
|
|
@@ -21351,7 +22312,7 @@ var init_api_mcp = __esm({
|
|
|
21351
22312
|
});
|
|
21352
22313
|
|
|
21353
22314
|
// src/backends/api-common.ts
|
|
21354
|
-
import { streamText, stepCountIs, NoOutputGeneratedError } from "ai";
|
|
22315
|
+
import { streamText, stepCountIs, NoOutputGeneratedError, APICallError } from "ai";
|
|
21355
22316
|
function toModelMessage(msg) {
|
|
21356
22317
|
switch (msg.role) {
|
|
21357
22318
|
case "system":
|
|
@@ -21425,7 +22386,7 @@ var init_api_common = __esm({
|
|
|
21425
22386
|
} catch (err) {
|
|
21426
22387
|
log(`[api-common] MCP tools unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
21427
22388
|
}
|
|
21428
|
-
const tools2 = buildApiTools(chatId, permMode, mcpTools, getApiWebSearchEnabled(chatId));
|
|
22389
|
+
const tools2 = buildApiTools(chatId, permMode, mcpTools, getApiWebSearchEnabled(chatId), timeoutMs > 0 ? timeoutMs : void 0);
|
|
21429
22390
|
const hasTools = Object.keys(tools2).length > 0;
|
|
21430
22391
|
let abortSignal = signal;
|
|
21431
22392
|
let timeoutHandle;
|
|
@@ -21485,6 +22446,9 @@ var init_api_common = __esm({
|
|
|
21485
22446
|
const usage2 = await result.usage;
|
|
21486
22447
|
const providerMeta = await result.providerMetadata;
|
|
21487
22448
|
const cost = this.extractCost(providerMeta);
|
|
22449
|
+
if (fullText === "" && !usage2.inputTokens) {
|
|
22450
|
+
throw new Error(`${this.backendId}: empty response with no tokens \u2014 possible rate limit or quota exhaustion on free tier`);
|
|
22451
|
+
}
|
|
21488
22452
|
return {
|
|
21489
22453
|
text: fullText,
|
|
21490
22454
|
cost,
|
|
@@ -21494,6 +22458,9 @@ var init_api_common = __esm({
|
|
|
21494
22458
|
} : void 0
|
|
21495
22459
|
};
|
|
21496
22460
|
} catch (err) {
|
|
22461
|
+
if (err instanceof APICallError && err.statusCode === 429) {
|
|
22462
|
+
throw new Error(`${this.backendId}: rate limit (429) \u2014 free tier quota may be exhausted. Retry later or switch to a paid model.`);
|
|
22463
|
+
}
|
|
21497
22464
|
if (abortSignal?.aborted || signal?.aborted || err instanceof Error && (err.name === "AbortError" || err.message.toLowerCase().includes("abort") || err.message.toLowerCase().includes("cancel"))) {
|
|
21498
22465
|
return { text: "", cost: null };
|
|
21499
22466
|
}
|
|
@@ -21629,18 +22596,26 @@ var init_ollama2 = __esm({
|
|
|
21629
22596
|
*/
|
|
21630
22597
|
async streamDirect(prompt, model2, opts) {
|
|
21631
22598
|
const cleanPrompt = stripForLocalModel(prompt);
|
|
21632
|
-
let
|
|
22599
|
+
let disableThinking = false;
|
|
21633
22600
|
try {
|
|
21634
22601
|
const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
|
|
21635
22602
|
const modelRecord = OllamaStore.getModelByName(model2);
|
|
21636
|
-
|
|
22603
|
+
if (modelRecord?.forceThinkOff) {
|
|
22604
|
+
disableThinking = true;
|
|
22605
|
+
} else if (opts?.thinkingLevel === "off") {
|
|
22606
|
+
disableThinking = true;
|
|
22607
|
+
}
|
|
21637
22608
|
} catch {
|
|
21638
22609
|
}
|
|
21639
22610
|
const apiOpts = {
|
|
21640
22611
|
timeoutMs: opts?.timeoutMs,
|
|
21641
22612
|
onStream: opts?.onStream,
|
|
21642
22613
|
signal: opts?.signal,
|
|
21643
|
-
|
|
22614
|
+
messageHistory: opts?.messageHistory,
|
|
22615
|
+
permMode: opts?.permMode,
|
|
22616
|
+
thinkingLevel: opts?.thinkingLevel,
|
|
22617
|
+
onThinking: opts?.onThinking,
|
|
22618
|
+
...disableThinking ? { providerOptions: { ollama: { think: false } } } : {}
|
|
21644
22619
|
};
|
|
21645
22620
|
const result = await this.streamDirectWithHistory(
|
|
21646
22621
|
cleanPrompt,
|
|
@@ -21665,9 +22640,10 @@ var init_ollama2 = __esm({
|
|
|
21665
22640
|
const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
|
|
21666
22641
|
const models = OllamaStore.getAvailableModels();
|
|
21667
22642
|
for (const m of models) {
|
|
22643
|
+
const isThinkingCapable = m.capability === "thinking" && !m.forceThinkOff;
|
|
21668
22644
|
this.availableModels[m.name] = {
|
|
21669
22645
|
label: `${m.name}${m.parameterSize ? ` (${m.parameterSize})` : ""}`,
|
|
21670
|
-
thinking: "none"
|
|
22646
|
+
thinking: isThinkingCapable ? "adjustable" : "none"
|
|
21671
22647
|
};
|
|
21672
22648
|
this.pricing[m.name] = { in: 0, out: 0, cache: 0 };
|
|
21673
22649
|
this.contextWindow[m.name] = m.contextWindow ?? 4096;
|
|
@@ -21962,7 +22938,7 @@ var init_backends = __esm({
|
|
|
21962
22938
|
ollama: new OllamaAdapter(),
|
|
21963
22939
|
openrouter: openRouterAdapter
|
|
21964
22940
|
};
|
|
21965
|
-
CHAT_BACKEND_IDS = ["claude", "gemini", "codex", "cursor", "openrouter"];
|
|
22941
|
+
CHAT_BACKEND_IDS = ["claude", "gemini", "codex", "cursor", "openrouter", "ollama"];
|
|
21966
22942
|
availableSet = /* @__PURE__ */ new Set();
|
|
21967
22943
|
}
|
|
21968
22944
|
});
|
|
@@ -22158,14 +23134,14 @@ var init_delivery = __esm({
|
|
|
22158
23134
|
});
|
|
22159
23135
|
|
|
22160
23136
|
// src/scheduler/retry.ts
|
|
22161
|
-
import { APICallError } from "ai";
|
|
23137
|
+
import { APICallError as APICallError2 } from "ai";
|
|
22162
23138
|
function isExhaustedMessage(text) {
|
|
22163
23139
|
return EXHAUSTED_PATTERNS.some((p) => p.test(text));
|
|
22164
23140
|
}
|
|
22165
23141
|
function classifyError(err) {
|
|
22166
23142
|
const msg = err instanceof Error ? err.message : String(err);
|
|
22167
23143
|
if (/spawn timeout/i.test(msg)) return "permanent";
|
|
22168
|
-
if (err instanceof
|
|
23144
|
+
if (err instanceof APICallError2 && err.isRetryable) {
|
|
22169
23145
|
return "transient";
|
|
22170
23146
|
}
|
|
22171
23147
|
for (const pattern of EXHAUSTED_PATTERNS) {
|
|
@@ -22577,7 +23553,7 @@ async function classifyIntentAsync(text, chatId) {
|
|
|
22577
23553
|
return "agentic";
|
|
22578
23554
|
}
|
|
22579
23555
|
var intentCounts, CHAT_EXACT, MUTATION_PATTERNS, CHAT_QUESTION_PATTERNS, STRUCTURAL_PATTERNS, LLM_CLASSIFY_PROMPT, LLM_CLASSIFY_TIMEOUT_MS;
|
|
22580
|
-
var
|
|
23556
|
+
var init_classify2 = __esm({
|
|
22581
23557
|
"src/intent/classify.ts"() {
|
|
22582
23558
|
"use strict";
|
|
22583
23559
|
init_store5();
|
|
@@ -23324,7 +24300,7 @@ function classifyAgentIntent(message) {
|
|
|
23324
24300
|
return NOT_DETECTED;
|
|
23325
24301
|
}
|
|
23326
24302
|
var AGENT_SIGNAL_PATTERNS, CLAW_SIGNAL_PATTERNS, NEGATIVE_PATTERNS;
|
|
23327
|
-
var
|
|
24303
|
+
var init_classify3 = __esm({
|
|
23328
24304
|
"src/agents/classify.ts"() {
|
|
23329
24305
|
"use strict";
|
|
23330
24306
|
AGENT_SIGNAL_PATTERNS = [
|
|
@@ -23559,13 +24535,152 @@ var init_session_log2 = __esm({
|
|
|
23559
24535
|
}
|
|
23560
24536
|
});
|
|
23561
24537
|
|
|
23562
|
-
// src/
|
|
23563
|
-
|
|
23564
|
-
|
|
24538
|
+
// src/channels/edit-coordinator.ts
|
|
24539
|
+
var edit_coordinator_exports = {};
|
|
24540
|
+
__export(edit_coordinator_exports, {
|
|
24541
|
+
getEditCoordinator: () => getEditCoordinator,
|
|
24542
|
+
resetEditCoordinator: () => resetEditCoordinator
|
|
24543
|
+
});
|
|
24544
|
+
function getEditCoordinator() {
|
|
24545
|
+
return EditCoordinator.getInstance();
|
|
23565
24546
|
}
|
|
23566
|
-
function
|
|
23567
|
-
|
|
24547
|
+
function resetEditCoordinator() {
|
|
24548
|
+
EditCoordinator.resetInstance();
|
|
23568
24549
|
}
|
|
24550
|
+
var TICK_INTERVAL_MS, MAX_EDITS_PER_WINDOW, EDIT_WINDOW_MS, EditCoordinator;
|
|
24551
|
+
var init_edit_coordinator = __esm({
|
|
24552
|
+
"src/channels/edit-coordinator.ts"() {
|
|
24553
|
+
"use strict";
|
|
24554
|
+
init_log();
|
|
24555
|
+
TICK_INTERVAL_MS = 1e3;
|
|
24556
|
+
MAX_EDITS_PER_WINDOW = 4;
|
|
24557
|
+
EDIT_WINDOW_MS = 6e4;
|
|
24558
|
+
EditCoordinator = class _EditCoordinator {
|
|
24559
|
+
static instance = null;
|
|
24560
|
+
/** Active streams indexed by messageId. */
|
|
24561
|
+
activeStreams = /* @__PURE__ */ new Map();
|
|
24562
|
+
/** Per-message edit tracking for the sliding window cap. */
|
|
24563
|
+
perMessageEditCount = /* @__PURE__ */ new Map();
|
|
24564
|
+
/** Single flush timer shared across all streams. */
|
|
24565
|
+
flushTimer = null;
|
|
24566
|
+
/** Round-robin index — cycles through stream keys. */
|
|
24567
|
+
roundRobinIndex = 0;
|
|
24568
|
+
/** Ordered list of stream keys for round-robin iteration.
|
|
24569
|
+
* Rebuilt on register/unregister to avoid iterator invalidation. */
|
|
24570
|
+
streamKeys = [];
|
|
24571
|
+
constructor() {
|
|
24572
|
+
}
|
|
24573
|
+
static getInstance() {
|
|
24574
|
+
if (!_EditCoordinator.instance) {
|
|
24575
|
+
_EditCoordinator.instance = new _EditCoordinator();
|
|
24576
|
+
}
|
|
24577
|
+
return _EditCoordinator.instance;
|
|
24578
|
+
}
|
|
24579
|
+
/** Reset the singleton (for testing only). */
|
|
24580
|
+
static resetInstance() {
|
|
24581
|
+
if (_EditCoordinator.instance) {
|
|
24582
|
+
_EditCoordinator.instance.shutdown();
|
|
24583
|
+
_EditCoordinator.instance = null;
|
|
24584
|
+
}
|
|
24585
|
+
}
|
|
24586
|
+
/** Register a stream to be managed by the coordinator.
|
|
24587
|
+
* Creates the flush timer if this is the first stream. */
|
|
24588
|
+
register(messageId, stream) {
|
|
24589
|
+
this.activeStreams.set(messageId, stream);
|
|
24590
|
+
this.rebuildKeys();
|
|
24591
|
+
log(`[edit-coordinator] registered stream ${messageId} (${this.activeStreams.size} active)`);
|
|
24592
|
+
if (!this.flushTimer) {
|
|
24593
|
+
this.flushTimer = setInterval(() => this.tick(), TICK_INTERVAL_MS);
|
|
24594
|
+
log(`[edit-coordinator] timer started (${TICK_INTERVAL_MS}ms tick)`);
|
|
24595
|
+
}
|
|
24596
|
+
}
|
|
24597
|
+
/** Unregister a stream (called on finalization).
|
|
24598
|
+
* Cleans up per-message edit tracking. Stops timer if no streams remain. */
|
|
24599
|
+
unregister(messageId) {
|
|
24600
|
+
this.activeStreams.delete(messageId);
|
|
24601
|
+
this.perMessageEditCount.delete(messageId);
|
|
24602
|
+
this.rebuildKeys();
|
|
24603
|
+
log(`[edit-coordinator] unregistered stream ${messageId} (${this.activeStreams.size} remaining)`);
|
|
24604
|
+
if (this.activeStreams.size === 0 && this.flushTimer) {
|
|
24605
|
+
clearInterval(this.flushTimer);
|
|
24606
|
+
this.flushTimer = null;
|
|
24607
|
+
this.roundRobinIndex = 0;
|
|
24608
|
+
log(`[edit-coordinator] timer stopped (no active streams)`);
|
|
24609
|
+
}
|
|
24610
|
+
}
|
|
24611
|
+
/** Shut down the coordinator — clear timer, remove all streams. */
|
|
24612
|
+
shutdown() {
|
|
24613
|
+
if (this.flushTimer) {
|
|
24614
|
+
clearInterval(this.flushTimer);
|
|
24615
|
+
this.flushTimer = null;
|
|
24616
|
+
}
|
|
24617
|
+
this.activeStreams.clear();
|
|
24618
|
+
this.perMessageEditCount.clear();
|
|
24619
|
+
this.streamKeys = [];
|
|
24620
|
+
this.roundRobinIndex = 0;
|
|
24621
|
+
}
|
|
24622
|
+
/** Get the number of active streams (for diagnostics). */
|
|
24623
|
+
get streamCount() {
|
|
24624
|
+
return this.activeStreams.size;
|
|
24625
|
+
}
|
|
24626
|
+
/** Check whether a message can be edited (under the per-message cap). */
|
|
24627
|
+
canEditMessage(messageId) {
|
|
24628
|
+
const record = this.perMessageEditCount.get(messageId);
|
|
24629
|
+
if (!record) return true;
|
|
24630
|
+
const now = Date.now();
|
|
24631
|
+
if (now - record.windowStart >= EDIT_WINDOW_MS) {
|
|
24632
|
+
return true;
|
|
24633
|
+
}
|
|
24634
|
+
return record.count < MAX_EDITS_PER_WINDOW;
|
|
24635
|
+
}
|
|
24636
|
+
/** Record that an edit was made to a message. */
|
|
24637
|
+
recordEdit(messageId) {
|
|
24638
|
+
const now = Date.now();
|
|
24639
|
+
const record = this.perMessageEditCount.get(messageId);
|
|
24640
|
+
if (!record || now - record.windowStart >= EDIT_WINDOW_MS) {
|
|
24641
|
+
this.perMessageEditCount.set(messageId, { count: 1, windowStart: now });
|
|
24642
|
+
} else {
|
|
24643
|
+
record.count++;
|
|
24644
|
+
}
|
|
24645
|
+
}
|
|
24646
|
+
// ── Internal ──────────────────────────────────────────────────────────
|
|
24647
|
+
/** Rebuild the ordered keys array after registration changes. */
|
|
24648
|
+
rebuildKeys() {
|
|
24649
|
+
this.streamKeys = Array.from(this.activeStreams.keys());
|
|
24650
|
+
if (this.streamKeys.length > 0) {
|
|
24651
|
+
this.roundRobinIndex = this.roundRobinIndex % this.streamKeys.length;
|
|
24652
|
+
} else {
|
|
24653
|
+
this.roundRobinIndex = 0;
|
|
24654
|
+
}
|
|
24655
|
+
}
|
|
24656
|
+
/** Timer tick — pick the next stream via round-robin and flush it.
|
|
24657
|
+
* If the selected stream is at its per-message edit cap, try the next one. */
|
|
24658
|
+
async tick() {
|
|
24659
|
+
if (this.streamKeys.length === 0) return;
|
|
24660
|
+
const startIdx = this.roundRobinIndex;
|
|
24661
|
+
let tried = 0;
|
|
24662
|
+
while (tried < this.streamKeys.length) {
|
|
24663
|
+
const idx = (startIdx + tried) % this.streamKeys.length;
|
|
24664
|
+
const messageId = this.streamKeys[idx];
|
|
24665
|
+
const stream = this.activeStreams.get(messageId);
|
|
24666
|
+
if (stream && this.canEditMessage(messageId)) {
|
|
24667
|
+
this.roundRobinIndex = (idx + 1) % this.streamKeys.length;
|
|
24668
|
+
try {
|
|
24669
|
+
await stream.flush();
|
|
24670
|
+
this.recordEdit(messageId);
|
|
24671
|
+
} catch {
|
|
24672
|
+
}
|
|
24673
|
+
return;
|
|
24674
|
+
}
|
|
24675
|
+
tried++;
|
|
24676
|
+
}
|
|
24677
|
+
this.roundRobinIndex = (startIdx + 1) % this.streamKeys.length;
|
|
24678
|
+
}
|
|
24679
|
+
};
|
|
24680
|
+
}
|
|
24681
|
+
});
|
|
24682
|
+
|
|
24683
|
+
// src/router/live-status.ts
|
|
23569
24684
|
function dedupThinking(entries) {
|
|
23570
24685
|
const out = [];
|
|
23571
24686
|
for (const e of entries) {
|
|
@@ -23629,18 +24744,15 @@ function makeLiveStatus(chatId, channel, modelLabel, verboseLevel, showThinking)
|
|
|
23629
24744
|
};
|
|
23630
24745
|
return { liveStatus, toolCb };
|
|
23631
24746
|
}
|
|
23632
|
-
var
|
|
24747
|
+
var MAX_THINKING_CHARS, TRIM_THRESHOLD, MAX_ENTRIES, SPINNER_FRAMES, HEARTBEAT_TEXTS, LiveStatusMessage;
|
|
23633
24748
|
var init_live_status = __esm({
|
|
23634
24749
|
"src/router/live-status.ts"() {
|
|
23635
24750
|
"use strict";
|
|
23636
24751
|
init_log();
|
|
23637
24752
|
init_helpers();
|
|
23638
24753
|
init_telegram_throttle();
|
|
23639
|
-
|
|
23640
|
-
FLUSH_INTERVAL_GROUP_MS = 5e3;
|
|
24754
|
+
init_edit_coordinator();
|
|
23641
24755
|
MAX_THINKING_CHARS = 800;
|
|
23642
|
-
GLOBAL_MIN_GAP_MS = 1e3;
|
|
23643
|
-
globalLastFlushAt = 0;
|
|
23644
24756
|
TRIM_THRESHOLD = 3500;
|
|
23645
24757
|
MAX_ENTRIES = 200;
|
|
23646
24758
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -23656,7 +24768,6 @@ var init_live_status = __esm({
|
|
|
23656
24768
|
messageId = null;
|
|
23657
24769
|
entries = [];
|
|
23658
24770
|
startTime = Date.now();
|
|
23659
|
-
flushTimer = null;
|
|
23660
24771
|
lastRendered = "";
|
|
23661
24772
|
finalized = false;
|
|
23662
24773
|
/** Earliest time the next flush is allowed (set after 429 backoff) */
|
|
@@ -23671,18 +24782,12 @@ var init_live_status = __esm({
|
|
|
23671
24782
|
/** Spinner frame counter — advances on each flush for animation. */
|
|
23672
24783
|
spinnerFrame = 0;
|
|
23673
24784
|
/** Timestamp of last successful edit — used for heartbeat force-through. */
|
|
23674
|
-
lastSuccessfulFlushAt = Date.now();
|
|
23675
24785
|
/** Callback to restart typing indicator as fallback. */
|
|
23676
24786
|
onTypingFallback;
|
|
23677
24787
|
/** Set a callback that restarts the typing indicator loop as a fallback. */
|
|
23678
24788
|
setTypingFallback(cb) {
|
|
23679
24789
|
this.onTypingFallback = cb;
|
|
23680
24790
|
}
|
|
23681
|
-
/** Resolve flush interval based on chat type (group chats are rate-limited more aggressively). */
|
|
23682
|
-
get flushIntervalMs() {
|
|
23683
|
-
const numericId = parseInt(this.chatId);
|
|
23684
|
-
return numericId < 0 ? FLUSH_INTERVAL_GROUP_MS : FLUSH_INTERVAL_DM_MS;
|
|
23685
|
-
}
|
|
23686
24791
|
/** Send the initial status message. Must be called before adding entries. */
|
|
23687
24792
|
async init() {
|
|
23688
24793
|
if (!this.channel.sendTextReturningId) return;
|
|
@@ -23690,9 +24795,7 @@ var init_live_status = __esm({
|
|
|
23690
24795
|
const initial = `\u23F3 ${this.modelLabel} \xB7 Processing\u2026`;
|
|
23691
24796
|
this.messageId = await this.channel.sendTextReturningId(this.chatId, initial, "plain") ?? null;
|
|
23692
24797
|
if (this.messageId) {
|
|
23693
|
-
this.
|
|
23694
|
-
this.flushTimer = setInterval(() => this.flush().catch(() => {
|
|
23695
|
-
}), this.flushIntervalMs);
|
|
24798
|
+
getEditCoordinator().register(this.messageId, this);
|
|
23696
24799
|
}
|
|
23697
24800
|
} catch (err) {
|
|
23698
24801
|
log(`[live-status] init failed: ${err}`);
|
|
@@ -23744,21 +24847,22 @@ var init_live_status = __esm({
|
|
|
23744
24847
|
}
|
|
23745
24848
|
/**
|
|
23746
24849
|
* Finalize the status message: replace the spinner with ✅ and elapsed time.
|
|
23747
|
-
*
|
|
24850
|
+
* Unregisters from EditCoordinator and sends one final P0_CRITICAL edit that
|
|
24851
|
+
* bypasses the coordinator entirely (finalization must never be skipped).
|
|
24852
|
+
* No-op if no message was created (channel doesn't support editing).
|
|
23748
24853
|
*/
|
|
23749
24854
|
async finalize(elapsedMs) {
|
|
23750
24855
|
this.finalized = true;
|
|
23751
24856
|
this.pendingTools.clear();
|
|
23752
|
-
if (this.
|
|
23753
|
-
|
|
23754
|
-
this.flushTimer = null;
|
|
24857
|
+
if (this.messageId) {
|
|
24858
|
+
getEditCoordinator().unregister(this.messageId);
|
|
23755
24859
|
}
|
|
23756
24860
|
if (!this.messageId || !this.channel.editText) return;
|
|
23757
24861
|
const elapsedSec = (elapsedMs / 1e3).toFixed(1);
|
|
23758
24862
|
const deduped = dedupThinking(this.entries);
|
|
23759
24863
|
const body = renderFinal(deduped, this.modelLabel, elapsedSec, this.hasTrimmed);
|
|
23760
24864
|
try {
|
|
23761
|
-
await this.channel.editText(this.chatId, this.messageId, body, "plain");
|
|
24865
|
+
await this.channel.editText(this.chatId, this.messageId, body, "plain", 0 /* P0_CRITICAL */);
|
|
23762
24866
|
} catch (err) {
|
|
23763
24867
|
log(`[live-status] finalize edit failed: ${err}`);
|
|
23764
24868
|
}
|
|
@@ -23767,7 +24871,16 @@ var init_live_status = __esm({
|
|
|
23767
24871
|
getMessageId() {
|
|
23768
24872
|
return this.messageId;
|
|
23769
24873
|
}
|
|
23770
|
-
// ──
|
|
24874
|
+
// ── FlushableStream interface ──────────────────────────────────────────
|
|
24875
|
+
/** Return the chatId this stream belongs to (FlushableStream interface). */
|
|
24876
|
+
getChatId() {
|
|
24877
|
+
return this.chatId;
|
|
24878
|
+
}
|
|
24879
|
+
/**
|
|
24880
|
+
* Flush the current status to Telegram via editMessageText.
|
|
24881
|
+
* Called by the EditCoordinator on each round-robin tick.
|
|
24882
|
+
* Public to satisfy FlushableStream interface — do not call directly.
|
|
24883
|
+
*/
|
|
23771
24884
|
async flush() {
|
|
23772
24885
|
if (this.finalized || !this.messageId || !this.channel.editText) return;
|
|
23773
24886
|
if (this.consecutiveEditFailures >= _LiveStatusMessage.MAX_EDIT_FAILURES) {
|
|
@@ -23780,10 +24893,9 @@ var init_live_status = __esm({
|
|
|
23780
24893
|
if (Date.now() < this.nextFlushAllowedAt) return;
|
|
23781
24894
|
const throttleState = getThrottleState();
|
|
23782
24895
|
if (throttleState?.isPaused) return;
|
|
23783
|
-
if (!canFlushGlobally()) return;
|
|
23784
24896
|
this.spinnerFrame++;
|
|
23785
24897
|
const deduped = dedupThinking(this.entries);
|
|
23786
|
-
|
|
24898
|
+
let body = renderEntries(
|
|
23787
24899
|
deduped,
|
|
23788
24900
|
this.modelLabel,
|
|
23789
24901
|
Date.now() - this.startTime,
|
|
@@ -23791,17 +24903,21 @@ var init_live_status = __esm({
|
|
|
23791
24903
|
this.pendingTools,
|
|
23792
24904
|
this.spinnerFrame
|
|
23793
24905
|
);
|
|
24906
|
+
if (throttleState && throttleState.queueDepth > 30) {
|
|
24907
|
+
body += `
|
|
24908
|
+
(queue: ${throttleState.queueDepth})`;
|
|
24909
|
+
}
|
|
23794
24910
|
if (body === this.lastRendered) return;
|
|
23795
24911
|
this.lastRendered = body;
|
|
23796
|
-
markGlobalFlush();
|
|
23797
24912
|
try {
|
|
23798
|
-
await this.channel.editText(this.chatId, this.messageId, body, "plain");
|
|
24913
|
+
await this.channel.editText(this.chatId, this.messageId, body, "plain", 2 /* P2_COSMETIC */);
|
|
23799
24914
|
this.consecutiveEditFailures = 0;
|
|
23800
24915
|
this.lastSuccessfulFlushAt = Date.now();
|
|
23801
24916
|
} catch (err) {
|
|
23802
24917
|
this.handleRateLimit(err);
|
|
23803
24918
|
}
|
|
23804
24919
|
}
|
|
24920
|
+
// ── Internal ──────────────────────────────────────────────────────────
|
|
23805
24921
|
/**
|
|
23806
24922
|
* Trim entries from the BEGINNING when the rendered body exceeds the threshold.
|
|
23807
24923
|
* This is the core of the single-message pattern: always show the most recent
|
|
@@ -24232,6 +25348,88 @@ var init_response = __esm({
|
|
|
24232
25348
|
}
|
|
24233
25349
|
});
|
|
24234
25350
|
|
|
25351
|
+
// src/channels/typing-manager.ts
|
|
25352
|
+
var typing_manager_exports = {};
|
|
25353
|
+
__export(typing_manager_exports, {
|
|
25354
|
+
getTypingManager: () => getTypingManager,
|
|
25355
|
+
resetTypingManager: () => resetTypingManager
|
|
25356
|
+
});
|
|
25357
|
+
function getTypingManager() {
|
|
25358
|
+
return TypingManager.getInstance();
|
|
25359
|
+
}
|
|
25360
|
+
function resetTypingManager() {
|
|
25361
|
+
TypingManager.resetInstance();
|
|
25362
|
+
}
|
|
25363
|
+
var TypingManager;
|
|
25364
|
+
var init_typing_manager = __esm({
|
|
25365
|
+
"src/channels/typing-manager.ts"() {
|
|
25366
|
+
"use strict";
|
|
25367
|
+
TypingManager = class _TypingManager {
|
|
25368
|
+
static instance = null;
|
|
25369
|
+
activeChats = /* @__PURE__ */ new Map();
|
|
25370
|
+
static getInstance() {
|
|
25371
|
+
if (!_TypingManager.instance) {
|
|
25372
|
+
_TypingManager.instance = new _TypingManager();
|
|
25373
|
+
}
|
|
25374
|
+
return _TypingManager.instance;
|
|
25375
|
+
}
|
|
25376
|
+
/**
|
|
25377
|
+
* Start showing typing indicator for this chat.
|
|
25378
|
+
* If already showing (another agent acquired), just increments refCount.
|
|
25379
|
+
*/
|
|
25380
|
+
acquire(chatId, channel) {
|
|
25381
|
+
const entry = this.activeChats.get(chatId);
|
|
25382
|
+
if (entry) {
|
|
25383
|
+
entry.refCount++;
|
|
25384
|
+
return;
|
|
25385
|
+
}
|
|
25386
|
+
channel.sendTyping?.(chatId).catch(() => {
|
|
25387
|
+
});
|
|
25388
|
+
const timer = setInterval(() => {
|
|
25389
|
+
channel.sendTyping?.(chatId).catch(() => {
|
|
25390
|
+
});
|
|
25391
|
+
}, 4e3);
|
|
25392
|
+
this.activeChats.set(chatId, { refCount: 1, timer });
|
|
25393
|
+
}
|
|
25394
|
+
/**
|
|
25395
|
+
* Stop showing typing for this agent's perspective.
|
|
25396
|
+
* Only stops the timer when refCount reaches 0.
|
|
25397
|
+
*/
|
|
25398
|
+
release(chatId) {
|
|
25399
|
+
const entry = this.activeChats.get(chatId);
|
|
25400
|
+
if (!entry) return;
|
|
25401
|
+
entry.refCount--;
|
|
25402
|
+
if (entry.refCount <= 0) {
|
|
25403
|
+
clearInterval(entry.timer);
|
|
25404
|
+
this.activeChats.delete(chatId);
|
|
25405
|
+
}
|
|
25406
|
+
}
|
|
25407
|
+
/** Clean shutdown — clear all timers. */
|
|
25408
|
+
shutdown() {
|
|
25409
|
+
for (const [, entry] of this.activeChats) {
|
|
25410
|
+
clearInterval(entry.timer);
|
|
25411
|
+
}
|
|
25412
|
+
this.activeChats.clear();
|
|
25413
|
+
}
|
|
25414
|
+
/** Expose active chat count for testing/diagnostics. */
|
|
25415
|
+
get size() {
|
|
25416
|
+
return this.activeChats.size;
|
|
25417
|
+
}
|
|
25418
|
+
/** Get ref count for a chat (for testing). */
|
|
25419
|
+
getRefCount(chatId) {
|
|
25420
|
+
return this.activeChats.get(chatId)?.refCount ?? 0;
|
|
25421
|
+
}
|
|
25422
|
+
/** Reset singleton (for testing only). */
|
|
25423
|
+
static resetInstance() {
|
|
25424
|
+
if (_TypingManager.instance) {
|
|
25425
|
+
_TypingManager.instance.shutdown();
|
|
25426
|
+
_TypingManager.instance = null;
|
|
25427
|
+
}
|
|
25428
|
+
}
|
|
25429
|
+
};
|
|
25430
|
+
}
|
|
25431
|
+
});
|
|
25432
|
+
|
|
24235
25433
|
// src/shell/exec.ts
|
|
24236
25434
|
import { execFile as execFile4 } from "child_process";
|
|
24237
25435
|
function resolveShell() {
|
|
@@ -24546,6 +25744,9 @@ async function handleVoice(msg, channel) {
|
|
|
24546
25744
|
await channel.sendText(chatId, "Couldn't transcribe the voice message. Make sure a transcription provider is configured via /voice.", { parseMode: "plain" });
|
|
24547
25745
|
return;
|
|
24548
25746
|
}
|
|
25747
|
+
if (getSttEcho(chatId)) {
|
|
25748
|
+
await channel.sendText(chatId, `\u{1F399} ${transcript}`, { parseMode: "plain" });
|
|
25749
|
+
}
|
|
24549
25750
|
const vBackendId = getBackend(chatId) ?? "claude";
|
|
24550
25751
|
const vLimitMsg = checkBackendLimits(vBackendId);
|
|
24551
25752
|
if (vLimitMsg) {
|
|
@@ -24829,168 +26030,6 @@ var init_media = __esm({
|
|
|
24829
26030
|
}
|
|
24830
26031
|
});
|
|
24831
26032
|
|
|
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
26033
|
// src/router/sidequest.ts
|
|
24995
26034
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
24996
26035
|
async function handleSideQuest(parentChatId, msg, channel) {
|
|
@@ -25006,18 +26045,7 @@ async function handleSideQuest(parentChatId, msg, channel) {
|
|
|
25006
26045
|
[{ label: "\u274C Cancel", data: `sq:cancel:${sqId}` }]
|
|
25007
26046
|
]);
|
|
25008
26047
|
const startTime = Date.now();
|
|
25009
|
-
|
|
25010
|
-
const typingLoop = async () => {
|
|
25011
|
-
while (typingActive) {
|
|
25012
|
-
try {
|
|
25013
|
-
await channel.sendTyping?.(parentChatId);
|
|
25014
|
-
} catch {
|
|
25015
|
-
}
|
|
25016
|
-
await new Promise((r) => setTimeout(r, 4e3));
|
|
25017
|
-
}
|
|
25018
|
-
};
|
|
25019
|
-
typingLoop().catch(() => {
|
|
25020
|
-
});
|
|
26048
|
+
getTypingManager().acquire(parentChatId, channel);
|
|
25021
26049
|
try {
|
|
25022
26050
|
const backend2 = getBackend(parentChatId);
|
|
25023
26051
|
const model2 = getModel(parentChatId);
|
|
@@ -25033,7 +26061,6 @@ async function handleSideQuest(parentChatId, msg, channel) {
|
|
|
25033
26061
|
entityType: "main",
|
|
25034
26062
|
bootstrapProfile: "interactive"
|
|
25035
26063
|
});
|
|
25036
|
-
typingActive = false;
|
|
25037
26064
|
const userText = msg.text ?? "";
|
|
25038
26065
|
const truncated = userText.length > 60 ? userText.slice(0, 57) + "\u2026" : userText;
|
|
25039
26066
|
const header2 = `\u{1F5FA} <b>Side quest: "${truncated}"</b>
|
|
@@ -25057,10 +26084,9 @@ async function handleSideQuest(parentChatId, msg, channel) {
|
|
|
25057
26084
|
log(`[reflection] Side quest signal detection error: ${e}`);
|
|
25058
26085
|
}
|
|
25059
26086
|
} catch (err) {
|
|
25060
|
-
typingActive = false;
|
|
25061
26087
|
await channel.sendText(parentChatId, `\u{1F5FA} Side quest failed: ${err.message}`, { parseMode: "plain" });
|
|
25062
26088
|
} finally {
|
|
25063
|
-
|
|
26089
|
+
getTypingManager().release(parentChatId);
|
|
25064
26090
|
const activeSet = activeSideQuests.get(parentChatId);
|
|
25065
26091
|
if (activeSet) {
|
|
25066
26092
|
activeSet.delete(sqId);
|
|
@@ -25072,6 +26098,7 @@ async function handleSideQuest(parentChatId, msg, channel) {
|
|
|
25072
26098
|
var init_sidequest = __esm({
|
|
25073
26099
|
"src/router/sidequest.ts"() {
|
|
25074
26100
|
"use strict";
|
|
26101
|
+
init_typing_manager();
|
|
25075
26102
|
init_agent();
|
|
25076
26103
|
init_log();
|
|
25077
26104
|
init_store5();
|
|
@@ -25513,19 +26540,27 @@ async function handleEvolveCallback(chatId, data, channel, messageId) {
|
|
|
25513
26540
|
break;
|
|
25514
26541
|
}
|
|
25515
26542
|
case "apply": {
|
|
25516
|
-
const
|
|
25517
|
-
|
|
25518
|
-
|
|
25519
|
-
|
|
25520
|
-
|
|
25521
|
-
|
|
25522
|
-
|
|
25523
|
-
|
|
25524
|
-
|
|
25525
|
-
|
|
25526
|
-
|
|
25527
|
-
|
|
25528
|
-
|
|
26543
|
+
const id = parseInt(idStr, 10);
|
|
26544
|
+
if (processingInsights.has(id)) break;
|
|
26545
|
+
processingInsights.add(id);
|
|
26546
|
+
await sendOrEditKeyboard(chatId, channel, messageId, "\u23F3 Applying...", []);
|
|
26547
|
+
try {
|
|
26548
|
+
const { applyInsight: applyInsight2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
|
|
26549
|
+
const { advanceReviewSession: advanceReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
26550
|
+
const result = await applyInsight2(id);
|
|
26551
|
+
advanceReviewSession2(getDb(), chatId, id, "applied");
|
|
26552
|
+
await sendOrEditKeyboard(
|
|
26553
|
+
chatId,
|
|
26554
|
+
channel,
|
|
26555
|
+
messageId,
|
|
26556
|
+
`\u2705 ${result.message}`,
|
|
26557
|
+
[]
|
|
26558
|
+
);
|
|
26559
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
26560
|
+
await sendCurrentProposalInPlace(chatId, channel, messageId);
|
|
26561
|
+
} finally {
|
|
26562
|
+
processingInsights.delete(id);
|
|
26563
|
+
}
|
|
25529
26564
|
break;
|
|
25530
26565
|
}
|
|
25531
26566
|
case "skip": {
|
|
@@ -25535,10 +26570,17 @@ async function handleEvolveCallback(chatId, data, channel, messageId) {
|
|
|
25535
26570
|
break;
|
|
25536
26571
|
}
|
|
25537
26572
|
case "reject": {
|
|
25538
|
-
const
|
|
25539
|
-
|
|
25540
|
-
|
|
25541
|
-
|
|
26573
|
+
const rejId = parseInt(idStr, 10);
|
|
26574
|
+
if (processingInsights.has(rejId)) break;
|
|
26575
|
+
processingInsights.add(rejId);
|
|
26576
|
+
try {
|
|
26577
|
+
const { updateInsightStatus: updateInsightStatus2, advanceReviewSession: advanceReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
26578
|
+
updateInsightStatus2(getDb(), rejId, "rejected");
|
|
26579
|
+
advanceReviewSession2(getDb(), chatId, rejId, "rejected");
|
|
26580
|
+
await sendCurrentProposalInPlace(chatId, channel, messageId);
|
|
26581
|
+
} finally {
|
|
26582
|
+
processingInsights.delete(rejId);
|
|
26583
|
+
}
|
|
25542
26584
|
break;
|
|
25543
26585
|
}
|
|
25544
26586
|
case "discuss": {
|
|
@@ -25785,11 +26827,13 @@ async function handleReflectCallback(chatId, data, channel, messageId) {
|
|
|
25785
26827
|
);
|
|
25786
26828
|
}
|
|
25787
26829
|
}
|
|
26830
|
+
var processingInsights;
|
|
25788
26831
|
var init_evolve2 = __esm({
|
|
25789
26832
|
"src/router/evolve.ts"() {
|
|
25790
26833
|
"use strict";
|
|
25791
26834
|
init_store5();
|
|
25792
26835
|
init_helpers();
|
|
26836
|
+
processingInsights = /* @__PURE__ */ new Set();
|
|
25793
26837
|
}
|
|
25794
26838
|
});
|
|
25795
26839
|
|
|
@@ -27954,15 +28998,16 @@ async function handleStopCommand(chatId, commandArgs, msg, channel) {
|
|
|
27954
28998
|
const stopped = stopAgent(chatId);
|
|
27955
28999
|
stopAllSideQuests(chatId);
|
|
27956
29000
|
cancelAllAgents(chatId);
|
|
29001
|
+
if (stopped) addForceStoppedChat(chatId);
|
|
27957
29002
|
await channel.sendText(
|
|
27958
29003
|
chatId,
|
|
27959
29004
|
stopped ? "Stopping current task..." : "Nothing is running.",
|
|
27960
|
-
{ parseMode: "plain", priority:
|
|
29005
|
+
{ parseMode: "plain", priority: 0 /* P0_CRITICAL */ }
|
|
27961
29006
|
);
|
|
27962
29007
|
if (stopped && typeof channel.sendKeyboard === "function") {
|
|
27963
29008
|
await channel.sendKeyboard(chatId, "", [
|
|
27964
29009
|
[{ label: "\u{1F195} New Chat", data: "menu:newchat" }]
|
|
27965
|
-
], { priority:
|
|
29010
|
+
], { priority: 0 /* P0_CRITICAL */ });
|
|
27966
29011
|
}
|
|
27967
29012
|
}
|
|
27968
29013
|
async function handleVoiceCommand(chatId, commandArgs, msg, channel) {
|
|
@@ -27984,7 +29029,7 @@ async function handleRememberCommand(chatId, commandArgs, msg, channel) {
|
|
|
27984
29029
|
}
|
|
27985
29030
|
const content = commandArgs.replace(/^that\s+/i, "");
|
|
27986
29031
|
const trigger = content.split(/\s+/).slice(0, 3).join(" ");
|
|
27987
|
-
|
|
29032
|
+
await remember(trigger, content);
|
|
27988
29033
|
await channel.sendText(chatId, "Got it, I'll remember that.", { parseMode: "plain" });
|
|
27989
29034
|
if (typeof channel.sendKeyboard === "function") {
|
|
27990
29035
|
await channel.sendKeyboard(chatId, "", [
|
|
@@ -28069,11 +29114,15 @@ async function handleStopallCommand(chatId, commandArgs, msg, channel) {
|
|
|
28069
29114
|
}
|
|
28070
29115
|
async function handleEditjobCommand(chatId, commandArgs, msg, channel) {
|
|
28071
29116
|
if (!commandArgs) {
|
|
28072
|
-
await
|
|
29117
|
+
await sendJobsBoard(chatId, channel, 1);
|
|
28073
29118
|
return;
|
|
28074
29119
|
}
|
|
28075
29120
|
const editId = parseInt(commandArgs, 10);
|
|
28076
|
-
|
|
29121
|
+
if (isNaN(editId)) {
|
|
29122
|
+
await sendJobsBoard(chatId, channel, 1);
|
|
29123
|
+
return;
|
|
29124
|
+
}
|
|
29125
|
+
await sendJobDetail(chatId, editId, channel);
|
|
28077
29126
|
}
|
|
28078
29127
|
async function handleJobsCommand(chatId, commandArgs, msg, channel) {
|
|
28079
29128
|
await sendJobsBoard(chatId, channel, 1);
|
|
@@ -28325,7 +29374,7 @@ async function handleClearCommand(chatId, _commandArgs, _msg, channel) {
|
|
|
28325
29374
|
clearChatPaidSlots(chatId);
|
|
28326
29375
|
setSessionStartedAt(chatId);
|
|
28327
29376
|
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "Session cleared (no summary)", detail: { field: "session", action: "clear" } });
|
|
28328
|
-
await channel.sendText(chatId, "\u{1F9FD} Session cleared. No summary saved.", { parseMode: "plain", priority:
|
|
29377
|
+
await channel.sendText(chatId, "\u{1F9FD} Session cleared. No summary saved.", { parseMode: "plain", priority: 0 /* P0_CRITICAL */ });
|
|
28329
29378
|
}
|
|
28330
29379
|
async function handleSummarizeCommand(chatId, commandArgs, msg, channel) {
|
|
28331
29380
|
if (commandArgs?.toLowerCase() === "all") {
|
|
@@ -28508,9 +29557,9 @@ async function handleStatusCommand(chatId, commandArgs, msg, channel) {
|
|
|
28508
29557
|
{ label: "Change Mode", data: "menu:permissions" },
|
|
28509
29558
|
{ label: "Change Style", data: "menu:style" }
|
|
28510
29559
|
]
|
|
28511
|
-
], { priority:
|
|
29560
|
+
], { priority: 0 /* P0_CRITICAL */ });
|
|
28512
29561
|
} else {
|
|
28513
|
-
await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain", priority:
|
|
29562
|
+
await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain", priority: 0 /* P0_CRITICAL */ });
|
|
28514
29563
|
}
|
|
28515
29564
|
}
|
|
28516
29565
|
async function handleBackendCommand2(chatId, commandArgs, msg, channel) {
|
|
@@ -28629,7 +29678,7 @@ async function handleMemoryCommand(chatId, commandArgs, msg, channel) {
|
|
|
28629
29678
|
return;
|
|
28630
29679
|
}
|
|
28631
29680
|
deleteMemoryById(editId);
|
|
28632
|
-
|
|
29681
|
+
await remember(mem.trigger, newContent, { category: mem.category });
|
|
28633
29682
|
await channel.sendText(chatId, `Memory #${editId} updated.`, { parseMode: "plain" });
|
|
28634
29683
|
return;
|
|
28635
29684
|
}
|
|
@@ -28814,9 +29863,9 @@ Recent directories:` : "Recent directories:";
|
|
|
28814
29863
|
const buttons = recents.map((r) => [{ label: r.alias, data: `cwdpick:${r.alias}` }]);
|
|
28815
29864
|
await channel.sendKeyboard(chatId, text, buttons);
|
|
28816
29865
|
} else {
|
|
28817
|
-
const
|
|
29866
|
+
const list2 = recents.map((r) => ` ${r.alias} \u2192 ${r.path}`).join("\n");
|
|
28818
29867
|
await channel.sendText(chatId, `${text}
|
|
28819
|
-
${
|
|
29868
|
+
${list2}`, { parseMode: "plain" });
|
|
28820
29869
|
}
|
|
28821
29870
|
return;
|
|
28822
29871
|
}
|
|
@@ -28971,12 +30020,12 @@ async function handleGeminiAccountsCommand(chatId, commandArgs, msg, channel) {
|
|
|
28971
30020
|
await channel.sendKeyboard(chatId, "Gemini Accounts & Rotation:", rows);
|
|
28972
30021
|
} else {
|
|
28973
30022
|
const currentMode = getGeminiRotationMode();
|
|
28974
|
-
const
|
|
30023
|
+
const list2 = slots.filter((s) => s.enabled).map((s) => {
|
|
28975
30024
|
const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
28976
30025
|
return `${icon} ${s.label || `slot-${s.id}`} (#${s.id})`;
|
|
28977
30026
|
}).join("\n");
|
|
28978
30027
|
await channel.sendText(chatId, `Slots:
|
|
28979
|
-
${
|
|
30028
|
+
${list2}
|
|
28980
30029
|
|
|
28981
30030
|
Rotation mode: ${currentMode}
|
|
28982
30031
|
Use: /gemini_accounts <name> to pin`, { parseMode: "plain" });
|
|
@@ -29003,12 +30052,12 @@ Add with: <code>cc-claw ${slotBackend} add-key</code>`, { parseMode: "html" });
|
|
|
29003
30052
|
await channel.sendKeyboard(chatId, `${slotDisplayName} Accounts & Rotation:`, rows);
|
|
29004
30053
|
} else {
|
|
29005
30054
|
const currentMode = getBackendRotationMode(slotBackend);
|
|
29006
|
-
const
|
|
30055
|
+
const list2 = slots.filter((s) => s.enabled).map((s) => {
|
|
29007
30056
|
const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
29008
30057
|
return `${icon} ${s.label || `slot-${s.id}`} (#${s.id})`;
|
|
29009
30058
|
}).join("\n");
|
|
29010
30059
|
await channel.sendText(chatId, `${slotDisplayName} Slots:
|
|
29011
|
-
${
|
|
30060
|
+
${list2}
|
|
29012
30061
|
|
|
29013
30062
|
Rotation mode: ${currentMode}
|
|
29014
30063
|
Use: /${command} <name> to pin`, { parseMode: "plain" });
|
|
@@ -29339,10 +30388,10 @@ async function handleTasksCommand(chatId, commandArgs, msg, channel) {
|
|
|
29339
30388
|
{ label: "Abandoned", emoji: "\u{1F6AB}", status: "abandoned" }
|
|
29340
30389
|
];
|
|
29341
30390
|
for (const { label: label2, emoji, status } of sections) {
|
|
29342
|
-
const
|
|
29343
|
-
if (
|
|
30391
|
+
const list2 = byStatus[status];
|
|
30392
|
+
if (list2.length === 0) continue;
|
|
29344
30393
|
lines.push(`${emoji} ${label2}:`);
|
|
29345
|
-
for (const t of
|
|
30394
|
+
for (const t of list2) {
|
|
29346
30395
|
const assignee = t.assignee ? ` (\u2192 ${t.assignee.slice(0, 8)})` : "";
|
|
29347
30396
|
lines.push(` #${t.id}: ${t.subject}${assignee}`);
|
|
29348
30397
|
}
|
|
@@ -29518,6 +30567,7 @@ async function handleCouncilCommand(chatId, commandArgs, msg, channel) {
|
|
|
29518
30567
|
var init_command_handlers = __esm({
|
|
29519
30568
|
"src/router/command-handlers.ts"() {
|
|
29520
30569
|
"use strict";
|
|
30570
|
+
init_telegram_throttle();
|
|
29521
30571
|
init_format();
|
|
29522
30572
|
init_log();
|
|
29523
30573
|
init_format_time();
|
|
@@ -29525,11 +30575,12 @@ var init_command_handlers = __esm({
|
|
|
29525
30575
|
init_image_gen();
|
|
29526
30576
|
init_stt();
|
|
29527
30577
|
init_agent();
|
|
29528
|
-
|
|
30578
|
+
init_classify2();
|
|
29529
30579
|
init_install();
|
|
29530
30580
|
init_profile();
|
|
29531
30581
|
init_heartbeat2();
|
|
29532
30582
|
init_discover();
|
|
30583
|
+
init_engine();
|
|
29533
30584
|
init_store5();
|
|
29534
30585
|
init_summarize();
|
|
29535
30586
|
init_session_log();
|
|
@@ -30662,6 +31713,9 @@ ${progressMsg}`,
|
|
|
30662
31713
|
}
|
|
30663
31714
|
setSttProvider(chatId, provider);
|
|
30664
31715
|
await sendVoiceConfigKeyboard(chatId, channel, messageId);
|
|
31716
|
+
} else if (data === "vcfg:echo") {
|
|
31717
|
+
toggleSttEcho(chatId);
|
|
31718
|
+
await sendVoiceConfigKeyboard(chatId, channel, messageId);
|
|
30665
31719
|
} else if (data.startsWith("vcfg:")) {
|
|
30666
31720
|
const parts = data.slice(5).split(":");
|
|
30667
31721
|
const action = parts[0];
|
|
@@ -30692,14 +31746,15 @@ ${progressMsg}`,
|
|
|
30692
31746
|
await handleWizardCallback(chatId, data, channel);
|
|
30693
31747
|
} else if (data.startsWith("job:")) {
|
|
30694
31748
|
async function showJobAccountPicker(cid, jobId, backend2, model2, thinking2, ch) {
|
|
30695
|
-
const
|
|
30696
|
-
const
|
|
31749
|
+
const { getAdapter: getAdapter3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
31750
|
+
const adapter = getAdapter3(backend2);
|
|
31751
|
+
const isApiBackend = adapter.type === "api";
|
|
31752
|
+
const isGemini = backend2 === BACKEND.GEMINI;
|
|
31753
|
+
const slots = isApiBackend ? [] : isGemini ? getGeminiSlots() : getBackendSlots(backend2);
|
|
30697
31754
|
const enabledSlots = slots.filter((s) => s.enabled);
|
|
30698
31755
|
if (enabledSlots.length === 0 || typeof ch.sendKeyboard !== "function") {
|
|
30699
31756
|
const { updateJob: updateJobFields } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
30700
31757
|
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
31758
|
await ch.sendText(cid, `Job #${jobId} updated: ${adapter.displayName} / ${model2} / ${thinking2}`, { parseMode: "plain" });
|
|
30704
31759
|
await sendJobDetail(cid, jobId, ch);
|
|
30705
31760
|
return;
|
|
@@ -31573,6 +32628,22 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
|
|
|
31573
32628
|
} else if (rest.startsWith("opt")) {
|
|
31574
32629
|
const { handleMemOptCallback: handleMemOptCallback2 } = await Promise.resolve().then(() => (init_optimize(), optimize_exports));
|
|
31575
32630
|
await handleMemOptCallback2(chatId, rest, channel, messageId);
|
|
32631
|
+
} else if (rest === "sweep:dismiss") {
|
|
32632
|
+
if (messageId && channel.editText) {
|
|
32633
|
+
await channel.editText(chatId, messageId, "\u2705 Memory sweep dismissed.", "plain").catch(() => {
|
|
32634
|
+
});
|
|
32635
|
+
}
|
|
32636
|
+
} else if (rest === "sweep:toggle") {
|
|
32637
|
+
const { getMetaValue: getMetaValue2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
32638
|
+
const { SWEEP_ENABLED_KEY: SWEEP_ENABLED_KEY2, enableSweep: enableSweep2, disableSweep: disableSweep2 } = await Promise.resolve().then(() => (init_sweep(), sweep_exports));
|
|
32639
|
+
const currentlyEnabled = getMetaValue2(SWEEP_ENABLED_KEY2) === "1";
|
|
32640
|
+
if (currentlyEnabled) {
|
|
32641
|
+
disableSweep2();
|
|
32642
|
+
} else {
|
|
32643
|
+
enableSweep2(chatId);
|
|
32644
|
+
}
|
|
32645
|
+
const { sendMemoryPage: sendMemoryPage3 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
|
|
32646
|
+
await sendMemoryPage3(chatId, channel, 1, messageId);
|
|
31576
32647
|
} else if (rest === "noop") {
|
|
31577
32648
|
}
|
|
31578
32649
|
return;
|
|
@@ -32489,7 +33560,9 @@ function withThread(channel, threadId) {
|
|
|
32489
33560
|
// These operate on existing messages — no threadId needed
|
|
32490
33561
|
editText: channel.editText?.bind(channel),
|
|
32491
33562
|
editKeyboard: channel.editKeyboard?.bind(channel),
|
|
32492
|
-
reactToMessage: channel.reactToMessage?.bind(channel)
|
|
33563
|
+
reactToMessage: channel.reactToMessage?.bind(channel),
|
|
33564
|
+
isDraftCapable: channel.isDraftCapable?.bind(channel),
|
|
33565
|
+
sendMessageDraft: channel.sendMessageDraft?.bind(channel)
|
|
32493
33566
|
};
|
|
32494
33567
|
}
|
|
32495
33568
|
var init_thread_wrapper = __esm({
|
|
@@ -33169,7 +34242,8 @@ Try a different keyword.`,
|
|
|
33169
34242
|
if (rememberMatch) {
|
|
33170
34243
|
const content = rememberMatch[1];
|
|
33171
34244
|
const trigger = content.split(/\s+/).slice(0, 3).join(" ");
|
|
33172
|
-
|
|
34245
|
+
remember(trigger, content).catch(() => {
|
|
34246
|
+
});
|
|
33173
34247
|
await channel.sendText(chatId, "Got it, I'll remember that.", { parseMode: "plain" });
|
|
33174
34248
|
return;
|
|
33175
34249
|
}
|
|
@@ -33410,18 +34484,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
|
|
|
33410
34484
|
isSideQuest: hasSqPrefix
|
|
33411
34485
|
})) {
|
|
33412
34486
|
const planDirective = buildPlanningDirective();
|
|
33413
|
-
|
|
33414
|
-
const typingLoop2 = async () => {
|
|
33415
|
-
while (typingActive2) {
|
|
33416
|
-
try {
|
|
33417
|
-
await channel.sendTyping?.(chatId);
|
|
33418
|
-
} catch {
|
|
33419
|
-
}
|
|
33420
|
-
await new Promise((r) => setTimeout(r, 4e3));
|
|
33421
|
-
}
|
|
33422
|
-
};
|
|
33423
|
-
typingLoop2().catch(() => {
|
|
33424
|
-
});
|
|
34487
|
+
getTypingManager().acquire(chatId, channel);
|
|
33425
34488
|
try {
|
|
33426
34489
|
const planResponse = await askAgent(chatId, cleanText || text, {
|
|
33427
34490
|
cwd: settings.getCwd(),
|
|
@@ -33431,7 +34494,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
|
|
|
33431
34494
|
agentMode: effectiveAgentMode,
|
|
33432
34495
|
planningDirective: planDirective
|
|
33433
34496
|
});
|
|
33434
|
-
|
|
34497
|
+
getTypingManager().release(chatId);
|
|
33435
34498
|
if (planResponse.text) {
|
|
33436
34499
|
let planText = planResponse.text.replace(/\[REACT:.+?\]/g, "").replace(/\[SEND_FILE:.+?\]/g, "").replace(/\[GENERATE_IMAGE:.+?\]/g, "").replace(/\[HISTORY_SEARCH:[^\]]+\]/g, "").trim();
|
|
33437
34500
|
const PLAN_DISPLAY_LIMIT = 3500;
|
|
@@ -33456,23 +34519,12 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
|
|
|
33456
34519
|
await channel.sendText(chatId, "(No plan generated \u2014 proceeding normally)", { parseMode: "plain" });
|
|
33457
34520
|
}
|
|
33458
34521
|
} catch (err) {
|
|
33459
|
-
|
|
34522
|
+
getTypingManager().release(chatId);
|
|
33460
34523
|
await channel.sendText(chatId, `\u26A0\uFE0F Planning error: ${err.message}`, { parseMode: "plain" });
|
|
33461
34524
|
}
|
|
33462
34525
|
return;
|
|
33463
34526
|
}
|
|
33464
|
-
|
|
33465
|
-
const typingLoop = async () => {
|
|
33466
|
-
while (typingActive) {
|
|
33467
|
-
try {
|
|
33468
|
-
await channel.sendTyping?.(chatId);
|
|
33469
|
-
} catch {
|
|
33470
|
-
}
|
|
33471
|
-
await new Promise((r) => setTimeout(r, 4e3));
|
|
33472
|
-
}
|
|
33473
|
-
};
|
|
33474
|
-
typingLoop().catch(() => {
|
|
33475
|
-
});
|
|
34527
|
+
getTypingManager().acquire(chatId, channel);
|
|
33476
34528
|
try {
|
|
33477
34529
|
const tMode = settings.getMode();
|
|
33478
34530
|
const tVerbose = settings.getVerboseLevel();
|
|
@@ -33730,7 +34782,7 @@ Approve paid usage for this session?`,
|
|
|
33730
34782
|
const userMsg = diagnoseAgentError(errMsg, chatId);
|
|
33731
34783
|
await channel.sendText(chatId, userMsg, { parseMode: "plain" });
|
|
33732
34784
|
} finally {
|
|
33733
|
-
|
|
34785
|
+
getTypingManager().release(chatId);
|
|
33734
34786
|
const pending = pendingInterrupts.get(chatId);
|
|
33735
34787
|
if (pending) {
|
|
33736
34788
|
pendingInterrupts.delete(chatId);
|
|
@@ -33748,12 +34800,13 @@ var init_router2 = __esm({
|
|
|
33748
34800
|
init_agent();
|
|
33749
34801
|
init_retry();
|
|
33750
34802
|
init_quota();
|
|
33751
|
-
|
|
34803
|
+
init_classify2();
|
|
34804
|
+
init_engine();
|
|
33752
34805
|
init_store5();
|
|
33753
34806
|
init_backends();
|
|
33754
34807
|
init_wizard();
|
|
33755
34808
|
init_ollama3();
|
|
33756
|
-
|
|
34809
|
+
init_classify3();
|
|
33757
34810
|
init_session_log2();
|
|
33758
34811
|
init_live_status();
|
|
33759
34812
|
init_detect();
|
|
@@ -33764,6 +34817,7 @@ var init_router2 = __esm({
|
|
|
33764
34817
|
init_gate();
|
|
33765
34818
|
init_helpers();
|
|
33766
34819
|
init_response();
|
|
34820
|
+
init_typing_manager();
|
|
33767
34821
|
init_shell();
|
|
33768
34822
|
init_ui();
|
|
33769
34823
|
init_api_models();
|
|
@@ -34021,6 +35075,28 @@ async function executeJob(job) {
|
|
|
34021
35075
|
async function runWithRetry(job, model2, runId, t0) {
|
|
34022
35076
|
let lastError;
|
|
34023
35077
|
const chatId = job.sessionType === "isolated" ? `cron:${job.id}:${runId}` : job.chatId;
|
|
35078
|
+
if (job.jobType === "memory_sweep") {
|
|
35079
|
+
const { runWeeklySweep: runWeeklySweep2 } = await Promise.resolve().then(() => (init_sweep(), sweep_exports));
|
|
35080
|
+
const { getChannelRegistry: getChannelRegistry2 } = await Promise.resolve().then(() => (init_delivery(), delivery_exports));
|
|
35081
|
+
const channelName = job.channel ?? "telegram";
|
|
35082
|
+
const channel = getChannelRegistry2()?.get(channelName);
|
|
35083
|
+
const deliveryTarget = job.target ?? job.chatId;
|
|
35084
|
+
if (!channel) {
|
|
35085
|
+
warn(`[sweep] Job #${job.id}: channel "${channelName}" not found \u2014 sweep ran silently`);
|
|
35086
|
+
}
|
|
35087
|
+
const result = await runWeeklySweep2(
|
|
35088
|
+
deliveryTarget,
|
|
35089
|
+
channel,
|
|
35090
|
+
resolveJobBackendId(job) ?? void 0,
|
|
35091
|
+
job.model ?? void 0
|
|
35092
|
+
);
|
|
35093
|
+
const parts = [];
|
|
35094
|
+
if (result.cleanedUp > 0) parts.push(`cleaned up ${result.cleanedUp} expired memories`);
|
|
35095
|
+
if (result.suggestionsCount > 0) parts.push(`found ${result.suggestionsCount} suggestions`);
|
|
35096
|
+
if (result.error) parts.push(`error: ${result.error}`);
|
|
35097
|
+
const summary = parts.length > 0 ? parts.join(", ") : "memory bank healthy, no action needed";
|
|
35098
|
+
return { text: summary };
|
|
35099
|
+
}
|
|
34024
35100
|
if (job.jobType === "reflection") {
|
|
34025
35101
|
const { runNightlyReflection: runNightlyReflection2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
|
|
34026
35102
|
const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
@@ -35036,6 +36112,11 @@ function isFastPathMessage(msg) {
|
|
|
35036
36112
|
function sanitizeForTelegram(text) {
|
|
35037
36113
|
return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFFFD\uFFFE\uFFFF]/g, "");
|
|
35038
36114
|
}
|
|
36115
|
+
function isDraftCapable(chatId) {
|
|
36116
|
+
if (isSyntheticChatId(chatId)) return false;
|
|
36117
|
+
const numId = numericChatId(chatId);
|
|
36118
|
+
return !isNaN(numId) && numId > 0;
|
|
36119
|
+
}
|
|
35039
36120
|
function numericChatId(chatId) {
|
|
35040
36121
|
if (chatId.startsWith("sq:") || chatId.startsWith("cron:")) {
|
|
35041
36122
|
throw new Error(`Synthetic chatId "${chatId}" passed to Telegram API`);
|
|
@@ -35051,6 +36132,7 @@ var init_telegram2 = __esm({
|
|
|
35051
36132
|
init_log();
|
|
35052
36133
|
init_health3();
|
|
35053
36134
|
init_store5();
|
|
36135
|
+
init_agent();
|
|
35054
36136
|
init_telegram_throttle();
|
|
35055
36137
|
FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat", "clear"]);
|
|
35056
36138
|
TelegramChannel = class _TelegramChannel {
|
|
@@ -35069,18 +36151,23 @@ var init_telegram2 = __esm({
|
|
|
35069
36151
|
mediaGroupBuffer = /* @__PURE__ */ new Map();
|
|
35070
36152
|
static MEDIA_GROUP_DEBOUNCE_MS = 500;
|
|
35071
36153
|
// ── Polling health tracking ─────────────────────────────────────────
|
|
35072
|
-
/** Timestamp of last
|
|
35073
|
-
|
|
36154
|
+
/** Timestamp of last successful API keepalive ping (updated every 2 minutes) */
|
|
36155
|
+
lastPollingCheckAt = 0;
|
|
35074
36156
|
/** True while polling is expected to be active (between start() and stop()) */
|
|
35075
36157
|
pollingExpected = false;
|
|
35076
36158
|
/** Watchdog interval that detects silent polling death */
|
|
35077
36159
|
pollingWatchdog = null;
|
|
35078
|
-
/**
|
|
35079
|
-
|
|
35080
|
-
|
|
36160
|
+
/** Keepalive interval: pings bot.api.getMe() to confirm API connection is alive */
|
|
36161
|
+
keepaliveInterval = null;
|
|
36162
|
+
/** Max time without a successful keepalive ping before we consider polling dead (ms) */
|
|
36163
|
+
static POLLING_SILENCE_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
36164
|
+
// 10 minutes
|
|
35081
36165
|
/** How often the watchdog checks for polling health (ms) */
|
|
35082
36166
|
static POLLING_WATCHDOG_INTERVAL_MS = 60 * 1e3;
|
|
35083
36167
|
// 60 seconds
|
|
36168
|
+
/** How often to ping bot.api.getMe() as a keepalive (ms) */
|
|
36169
|
+
static POLLING_KEEPALIVE_INTERVAL_MS = 2 * 60 * 1e3;
|
|
36170
|
+
// 2 minutes
|
|
35084
36171
|
constructor() {
|
|
35085
36172
|
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
35086
36173
|
if (!token) {
|
|
@@ -35198,7 +36285,6 @@ var init_telegram2 = __esm({
|
|
|
35198
36285
|
{ command: "council", description: "Multi-model debate (select models, anonymous rounds)" }
|
|
35199
36286
|
]);
|
|
35200
36287
|
this.bot.on("message", async (ctx) => {
|
|
35201
|
-
this.lastUpdateAt = Date.now();
|
|
35202
36288
|
const chatId = ctx.chat.id.toString();
|
|
35203
36289
|
const senderId = ctx.from?.id?.toString() ?? "";
|
|
35204
36290
|
const authorized = this.isAuthorized(chatId) || this.isAuthorized(senderId);
|
|
@@ -35239,7 +36325,6 @@ var init_telegram2 = __esm({
|
|
|
35239
36325
|
});
|
|
35240
36326
|
});
|
|
35241
36327
|
this.bot.on("callback_query:data", (ctx) => {
|
|
35242
|
-
this.lastUpdateAt = Date.now();
|
|
35243
36328
|
const userId = ctx.from.id.toString();
|
|
35244
36329
|
const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
|
|
35245
36330
|
log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
|
|
@@ -35267,7 +36352,6 @@ var init_telegram2 = __esm({
|
|
|
35267
36352
|
});
|
|
35268
36353
|
});
|
|
35269
36354
|
this.bot.on("message_reaction", async (ctx) => {
|
|
35270
|
-
this.lastUpdateAt = Date.now();
|
|
35271
36355
|
const chatId = String(ctx.chat.id);
|
|
35272
36356
|
const messageId = ctx.messageReaction.message_id;
|
|
35273
36357
|
if (!this.agentMessageIds.has(messageId)) return;
|
|
@@ -35284,7 +36368,6 @@ var init_telegram2 = __esm({
|
|
|
35284
36368
|
}
|
|
35285
36369
|
});
|
|
35286
36370
|
this.bot.on("inline_query", (ctx) => {
|
|
35287
|
-
this.lastUpdateAt = Date.now();
|
|
35288
36371
|
if (!this.isAuthorized(ctx.from.id.toString())) return;
|
|
35289
36372
|
this.handleInlineQuery(ctx).catch((err) => {
|
|
35290
36373
|
error("[telegram] Inline query error:", err);
|
|
@@ -35299,7 +36382,16 @@ var init_telegram2 = __esm({
|
|
|
35299
36382
|
}
|
|
35300
36383
|
});
|
|
35301
36384
|
this.pollingExpected = true;
|
|
35302
|
-
this.
|
|
36385
|
+
this.lastPollingCheckAt = Date.now();
|
|
36386
|
+
this.keepaliveInterval = setInterval(async () => {
|
|
36387
|
+
if (!this.pollingExpected) return;
|
|
36388
|
+
try {
|
|
36389
|
+
await this.bot.api.getMe();
|
|
36390
|
+
this.lastPollingCheckAt = Date.now();
|
|
36391
|
+
} catch (err) {
|
|
36392
|
+
error("[telegram] Keepalive ping failed:", err);
|
|
36393
|
+
}
|
|
36394
|
+
}, _TelegramChannel.POLLING_KEEPALIVE_INTERVAL_MS);
|
|
35303
36395
|
const pollingPromise = this.bot.start({
|
|
35304
36396
|
allowed_updates: [...API_CONSTANTS.ALL_UPDATE_TYPES],
|
|
35305
36397
|
onStart: () => log("[telegram] Polling for messages...")
|
|
@@ -35322,13 +36414,13 @@ var init_telegram2 = __esm({
|
|
|
35322
36414
|
);
|
|
35323
36415
|
this.pollingWatchdog = setInterval(() => {
|
|
35324
36416
|
if (!this.pollingExpected) return;
|
|
35325
|
-
const silenceMs = Date.now() - this.
|
|
36417
|
+
const silenceMs = Date.now() - this.lastPollingCheckAt;
|
|
35326
36418
|
if (silenceMs > _TelegramChannel.POLLING_SILENCE_THRESHOLD_MS) {
|
|
35327
36419
|
log(
|
|
35328
|
-
`[telegram] No
|
|
36420
|
+
`[telegram] No polling confirmation for ${Math.round(silenceMs / 1e3)}s \u2014 triggering reconnect`
|
|
35329
36421
|
);
|
|
35330
|
-
markChannelDown("telegram", `No
|
|
35331
|
-
this.
|
|
36422
|
+
markChannelDown("telegram", `No polling confirmation for ${Math.round(silenceMs / 1e3)}s`);
|
|
36423
|
+
this.lastPollingCheckAt = Date.now();
|
|
35332
36424
|
}
|
|
35333
36425
|
}, _TelegramChannel.POLLING_WATCHDOG_INTERVAL_MS);
|
|
35334
36426
|
}
|
|
@@ -35338,6 +36430,10 @@ var init_telegram2 = __esm({
|
|
|
35338
36430
|
clearInterval(this.pollingWatchdog);
|
|
35339
36431
|
this.pollingWatchdog = null;
|
|
35340
36432
|
}
|
|
36433
|
+
if (this.keepaliveInterval) {
|
|
36434
|
+
clearInterval(this.keepaliveInterval);
|
|
36435
|
+
this.keepaliveInterval = null;
|
|
36436
|
+
}
|
|
35341
36437
|
try {
|
|
35342
36438
|
await this.bot.stop();
|
|
35343
36439
|
} catch {
|
|
@@ -35451,15 +36547,18 @@ var init_telegram2 = __esm({
|
|
|
35451
36547
|
return void 0;
|
|
35452
36548
|
}
|
|
35453
36549
|
}
|
|
35454
|
-
async editText(chatId, messageId, text, parseMode) {
|
|
36550
|
+
async editText(chatId, messageId, text, parseMode, priority) {
|
|
35455
36551
|
const formatted = sanitizeForTelegram(parseMode === "html" ? text : formatForTelegram(text));
|
|
36552
|
+
const isCritical = priority === true || priority === 0 /* P0_CRITICAL */;
|
|
36553
|
+
const label2 = isCritical ? "finalizeStatus" : "editText:html";
|
|
35456
36554
|
try {
|
|
35457
36555
|
await this.throttle.send(
|
|
35458
36556
|
chatId,
|
|
35459
|
-
|
|
36557
|
+
label2,
|
|
35460
36558
|
() => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
|
|
35461
36559
|
parse_mode: "HTML"
|
|
35462
|
-
})
|
|
36560
|
+
}),
|
|
36561
|
+
priority
|
|
35463
36562
|
);
|
|
35464
36563
|
return true;
|
|
35465
36564
|
} catch (err) {
|
|
@@ -35472,12 +36571,13 @@ var init_telegram2 = __esm({
|
|
|
35472
36571
|
try {
|
|
35473
36572
|
await this.throttle.send(
|
|
35474
36573
|
chatId,
|
|
35475
|
-
"editText:fallback",
|
|
36574
|
+
priority ? "finalizeStatus:fallback" : "editText:fallback",
|
|
35476
36575
|
() => this.bot.api.editMessageText(
|
|
35477
36576
|
numericChatId(chatId),
|
|
35478
36577
|
parseInt(messageId),
|
|
35479
36578
|
formatted.replace(/<[^>]+>/g, "")
|
|
35480
|
-
)
|
|
36579
|
+
),
|
|
36580
|
+
priority
|
|
35481
36581
|
);
|
|
35482
36582
|
return true;
|
|
35483
36583
|
} catch (err2) {
|
|
@@ -35503,7 +36603,7 @@ var init_telegram2 = __esm({
|
|
|
35503
36603
|
}
|
|
35504
36604
|
return keyboard;
|
|
35505
36605
|
}
|
|
35506
|
-
async editKeyboard(chatId, messageId, text, buttons) {
|
|
36606
|
+
async editKeyboard(chatId, messageId, text, buttons, opts) {
|
|
35507
36607
|
const keyboard = this.buildInlineKeyboard(buttons);
|
|
35508
36608
|
const formatted = sanitizeForTelegram(formatForTelegram(text));
|
|
35509
36609
|
try {
|
|
@@ -35513,7 +36613,8 @@ var init_telegram2 = __esm({
|
|
|
35513
36613
|
() => this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
|
|
35514
36614
|
parse_mode: "HTML",
|
|
35515
36615
|
reply_markup: keyboard
|
|
35516
|
-
})
|
|
36616
|
+
}),
|
|
36617
|
+
opts?.priority
|
|
35517
36618
|
);
|
|
35518
36619
|
return true;
|
|
35519
36620
|
} catch (err) {
|
|
@@ -35527,7 +36628,8 @@ var init_telegram2 = __esm({
|
|
|
35527
36628
|
parseInt(messageId),
|
|
35528
36629
|
formatted.replace(/<[^>]+>/g, ""),
|
|
35529
36630
|
{ reply_markup: keyboard }
|
|
35530
|
-
)
|
|
36631
|
+
),
|
|
36632
|
+
opts?.priority
|
|
35531
36633
|
);
|
|
35532
36634
|
return true;
|
|
35533
36635
|
} catch (err2) {
|
|
@@ -35621,6 +36723,44 @@ var init_telegram2 = __esm({
|
|
|
35621
36723
|
log(`[telegram] reactToMessage failed (chat=${chatId} msg=${messageId}): ${err}`);
|
|
35622
36724
|
}
|
|
35623
36725
|
}
|
|
36726
|
+
/**
|
|
36727
|
+
* Check whether a chat supports native draft streaming.
|
|
36728
|
+
* sendMessageDraft only works in private/DM chats (positive chat IDs).
|
|
36729
|
+
*/
|
|
36730
|
+
isDraftCapable(chatId) {
|
|
36731
|
+
return isDraftCapable(chatId);
|
|
36732
|
+
}
|
|
36733
|
+
/**
|
|
36734
|
+
* Send a streaming draft update to a DM chat using Telegram's native
|
|
36735
|
+
* sendMessageDraft API (Bot API 9.3+). The draft shows an animated
|
|
36736
|
+
* typing bubble with the text content, replacing editMessageText for
|
|
36737
|
+
* DM streaming. Subsequent calls with the same draft_id produce smooth
|
|
36738
|
+
* animated transitions.
|
|
36739
|
+
*
|
|
36740
|
+
* Draft updates are cosmetic — if one gets dropped, the next update
|
|
36741
|
+
* will contain the full accumulated text. Uses tryBestEffort so drafts
|
|
36742
|
+
* never block critical sends.
|
|
36743
|
+
*
|
|
36744
|
+
* @param chatId - Private chat ID (must be positive / draft-capable)
|
|
36745
|
+
* @param draftId - Non-zero integer, consistent per response for smooth animation
|
|
36746
|
+
* @param text - Plain text content (no parse_mode during streaming)
|
|
36747
|
+
*/
|
|
36748
|
+
async sendMessageDraft(chatId, draftId, text) {
|
|
36749
|
+
if (!this.isDraftCapable(chatId)) {
|
|
36750
|
+
log(`[telegram] sendMessageDraft skipped \u2014 chat ${chatId} is not draft-capable`);
|
|
36751
|
+
return;
|
|
36752
|
+
}
|
|
36753
|
+
try {
|
|
36754
|
+
await this.throttle.tryBestEffort(
|
|
36755
|
+
chatId,
|
|
36756
|
+
"draft",
|
|
36757
|
+
() => this.bot.api.sendMessageDraft(numericChatId(chatId), draftId, sanitizeForTelegram(text))
|
|
36758
|
+
);
|
|
36759
|
+
log(`[telegram] sendMessageDraft sent (chat=${chatId}, draftId=${draftId}, len=${text.length})`);
|
|
36760
|
+
} catch (err) {
|
|
36761
|
+
log(`[telegram] sendMessageDraft failed (chat=${chatId}): ${err}`);
|
|
36762
|
+
}
|
|
36763
|
+
}
|
|
35624
36764
|
/** Get the underlying Grammy Bot instance (for scheduler, etc.) */
|
|
35625
36765
|
getBot() {
|
|
35626
36766
|
return this.bot;
|
|
@@ -36606,7 +37746,7 @@ async function main() {
|
|
|
36606
37746
|
pruneMessageLog(30, 2e3);
|
|
36607
37747
|
bootstrapBuiltinMcps(getDb());
|
|
36608
37748
|
try {
|
|
36609
|
-
const { resetIntentStats: resetIntentStats2 } = await Promise.resolve().then(() => (
|
|
37749
|
+
const { resetIntentStats: resetIntentStats2 } = await Promise.resolve().then(() => (init_classify2(), classify_exports));
|
|
36610
37750
|
resetIntentStats2();
|
|
36611
37751
|
} catch {
|
|
36612
37752
|
}
|
|
@@ -36782,6 +37922,22 @@ ${lines.join("\n")}`;
|
|
|
36782
37922
|
migrateEmbeddings().catch((err) => error("[cc-claw] Embedding migration failed:", err));
|
|
36783
37923
|
initHeartbeat(channelRegistry);
|
|
36784
37924
|
startAllHeartbeats();
|
|
37925
|
+
try {
|
|
37926
|
+
const { getMetaValue: getMetaValue2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
37927
|
+
const { SWEEP_ENABLED_KEY: SWEEP_ENABLED_KEY2, findSweepJob: findSweepJob2, enableSweep: enableSweep2 } = await Promise.resolve().then(() => (init_sweep(), sweep_exports));
|
|
37928
|
+
const sweepEnabled = getMetaValue2(SWEEP_ENABLED_KEY2);
|
|
37929
|
+
if (sweepEnabled === "1") {
|
|
37930
|
+
if (!findSweepJob2()) {
|
|
37931
|
+
const primaryChatId = (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim();
|
|
37932
|
+
if (primaryChatId) {
|
|
37933
|
+
enableSweep2(primaryChatId);
|
|
37934
|
+
log("[sweep] Restored weekly memory sweep job from meta setting");
|
|
37935
|
+
}
|
|
37936
|
+
}
|
|
37937
|
+
}
|
|
37938
|
+
} catch (err) {
|
|
37939
|
+
warn(`[sweep] Failed to restore sweep job: ${err instanceof Error ? err.message : String(err)}`);
|
|
37940
|
+
}
|
|
36785
37941
|
startHealthMonitor3(channelRegistry.list(), handleMessage);
|
|
36786
37942
|
Promise.resolve().then(() => (init_health(), health_exports)).then(({ startHealthMonitor: startMcpHealthMonitor }) => {
|
|
36787
37943
|
startMcpHealthMonitor(getDb());
|
|
@@ -36824,9 +37980,19 @@ ${lines.join("\n")}`;
|
|
|
36824
37980
|
} catch {
|
|
36825
37981
|
}
|
|
36826
37982
|
;
|
|
37983
|
+
try {
|
|
37984
|
+
const { getEditCoordinator: getEditCoordinator2 } = await Promise.resolve().then(() => (init_edit_coordinator(), edit_coordinator_exports));
|
|
37985
|
+
getEditCoordinator2().shutdown();
|
|
37986
|
+
} catch {
|
|
37987
|
+
}
|
|
37988
|
+
try {
|
|
37989
|
+
const { getTypingManager: getTypingManager2 } = await Promise.resolve().then(() => (init_typing_manager(), typing_manager_exports));
|
|
37990
|
+
getTypingManager2().shutdown();
|
|
37991
|
+
} catch {
|
|
37992
|
+
}
|
|
36827
37993
|
shutdownOrchestrator();
|
|
36828
37994
|
shutdownScheduler();
|
|
36829
|
-
|
|
37995
|
+
flushMemoryHalfLifeUpdates();
|
|
36830
37996
|
flushSummarySalienceUpdates();
|
|
36831
37997
|
await Promise.race([
|
|
36832
37998
|
summarizeAllPending(),
|
|
@@ -37383,6 +38549,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
37383
38549
|
const dbStat = existsSync35(DB_PATH) ? statSync10(DB_PATH) : null;
|
|
37384
38550
|
let daemonRunning = false;
|
|
37385
38551
|
let daemonInfo = {};
|
|
38552
|
+
let throttleData;
|
|
37386
38553
|
try {
|
|
37387
38554
|
const { apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
37388
38555
|
const healthRes = await apiGet2("/api/health");
|
|
@@ -37390,6 +38557,9 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
37390
38557
|
if (healthRes.ok && healthRes.data?.uptime) {
|
|
37391
38558
|
daemonInfo.uptime_seconds = Math.floor(healthRes.data.uptime);
|
|
37392
38559
|
}
|
|
38560
|
+
if (healthRes.ok && healthRes.data?.throttle) {
|
|
38561
|
+
throttleData = healthRes.data.throttle;
|
|
38562
|
+
}
|
|
37393
38563
|
} catch {
|
|
37394
38564
|
}
|
|
37395
38565
|
const contextUsed = (usageRow?.last_input_tokens ?? 0) + (usageRow?.last_cache_read_tokens ?? 0);
|
|
@@ -37411,7 +38581,8 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
37411
38581
|
output_tokens: usageRow?.output_tokens ?? 0,
|
|
37412
38582
|
cache_read_tokens: usageRow?.cache_read_tokens ?? 0
|
|
37413
38583
|
},
|
|
37414
|
-
db: { path: DB_PATH, sizeBytes: dbStat?.size ?? 0, exists: !!dbStat }
|
|
38584
|
+
db: { path: DB_PATH, sizeBytes: dbStat?.size ?? 0, exists: !!dbStat },
|
|
38585
|
+
throttle: throttleData
|
|
37415
38586
|
};
|
|
37416
38587
|
try {
|
|
37417
38588
|
const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
|
|
@@ -37443,6 +38614,14 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
37443
38614
|
if (localOpts.deep) {
|
|
37444
38615
|
lines.push(kvLine("Daemon", s.daemon.running ? success(`running${s.daemon.uptime_seconds ? ` (uptime ${formatUptime2(s.daemon.uptime_seconds)})` : ""}`) : error2("offline")));
|
|
37445
38616
|
}
|
|
38617
|
+
if (s.throttle) {
|
|
38618
|
+
const t = s.throttle;
|
|
38619
|
+
const queueStr = t.queueDepth > 0 ? warning(`${t.queueDepth} queued`) : success("0 queued");
|
|
38620
|
+
const pauseStr = t.isPaused ? error2(`PAUSED (${t.pauseRemainingSec}s remaining)`) : "";
|
|
38621
|
+
const circuitStr = t.circuitState !== "closed" ? warning(t.circuitState.toUpperCase()) : "";
|
|
38622
|
+
const parts = [queueStr, pauseStr, circuitStr].filter(Boolean).join(", ");
|
|
38623
|
+
lines.push(kvLine("Throttle", parts));
|
|
38624
|
+
}
|
|
37446
38625
|
lines.push(
|
|
37447
38626
|
"",
|
|
37448
38627
|
divider("Usage (this session)"),
|
|
@@ -37779,6 +38958,7 @@ async function logsCommand(opts) {
|
|
|
37779
38958
|
console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
|
|
37780
38959
|
console.log(tailLines.join("\n"));
|
|
37781
38960
|
if (opts.follow) {
|
|
38961
|
+
globalThis._cliStayAlive = true;
|
|
37782
38962
|
console.log(muted("\n Following... (Ctrl+C to stop)\n"));
|
|
37783
38963
|
let lastLength = content.length;
|
|
37784
38964
|
watchFile2(logFile, { interval: 500 }, () => {
|
|
@@ -37837,9 +39017,9 @@ async function sessionLogsList(opts) {
|
|
|
37837
39017
|
`));
|
|
37838
39018
|
console.log(` ${"Filename".padEnd(55)} ${"Size".padStart(8)} Chat ID`);
|
|
37839
39019
|
console.log(` ${"\u2500".repeat(55)} ${"\u2500".repeat(8)} ${"\u2500".repeat(15)}`);
|
|
37840
|
-
for (const
|
|
37841
|
-
const size =
|
|
37842
|
-
console.log(` ${
|
|
39020
|
+
for (const log5 of logs) {
|
|
39021
|
+
const size = log5.sizeBytes < 1024 ? `${log5.sizeBytes}B` : log5.sizeBytes < 1024 * 1024 ? `${(log5.sizeBytes / 1024).toFixed(1)}K` : `${(log5.sizeBytes / 1024 / 1024).toFixed(1)}M`;
|
|
39022
|
+
console.log(` ${log5.filename.padEnd(55)} ${size.padStart(8)} ${log5.chatId}`);
|
|
37843
39023
|
}
|
|
37844
39024
|
console.log(muted(`
|
|
37845
39025
|
Path: ${SESSION_LOGS_PATH}`));
|
|
@@ -37869,6 +39049,7 @@ async function sessionLogsTail(opts) {
|
|
|
37869
39049
|
console.log(line);
|
|
37870
39050
|
}
|
|
37871
39051
|
if (opts.follow) {
|
|
39052
|
+
globalThis._cliStayAlive = true;
|
|
37872
39053
|
console.log(muted("\n Following... (Ctrl+C to stop)\n"));
|
|
37873
39054
|
let lastLength = 0;
|
|
37874
39055
|
try {
|
|
@@ -37976,9 +39157,9 @@ async function geminiList(globalOpts) {
|
|
|
37976
39157
|
email: s.slot_type === "oauth" ? resolveOAuthEmail(s.config_home) : null
|
|
37977
39158
|
}));
|
|
37978
39159
|
output(enriched, (data) => {
|
|
37979
|
-
const
|
|
39160
|
+
const list2 = data;
|
|
37980
39161
|
const lines = ["", divider("Gemini Credential Slots"), ""];
|
|
37981
|
-
for (const s of
|
|
39162
|
+
for (const s of list2) {
|
|
37982
39163
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
37983
39164
|
const inCooldown = s.cooldown_until && s.cooldown_until > now;
|
|
37984
39165
|
const icon = !s.enabled ? error2("\u25CB disabled") : inCooldown ? warning("\u25D1 cooldown") : success("\u25CF active");
|
|
@@ -38301,7 +39482,7 @@ async function resolveSlotId2(backend2, idOrLabel) {
|
|
|
38301
39482
|
return match?.id ?? null;
|
|
38302
39483
|
}
|
|
38303
39484
|
function makeList(backend2, displayName) {
|
|
38304
|
-
return async function
|
|
39485
|
+
return async function list2(_globalOpts) {
|
|
38305
39486
|
requireDb2();
|
|
38306
39487
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
38307
39488
|
const readDb = openDatabaseReadOnly2();
|
|
@@ -38316,9 +39497,9 @@ Add one with: cc-claw ${backend2} add-account or cc-claw ${backend2} add-key`)
|
|
|
38316
39497
|
return;
|
|
38317
39498
|
}
|
|
38318
39499
|
output(slots, (data) => {
|
|
38319
|
-
const
|
|
39500
|
+
const list3 = data;
|
|
38320
39501
|
const lines = ["", divider(`${displayName} Credential Slots`), ""];
|
|
38321
|
-
for (const s of
|
|
39502
|
+
for (const s of list3) {
|
|
38322
39503
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
38323
39504
|
const inCooldown = s.cooldown_until && s.cooldown_until > now;
|
|
38324
39505
|
const icon = !s.enabled ? error2("\u25CB disabled") : inCooldown ? warning("\u25D1 cooldown") : success("\u25CF active");
|
|
@@ -38782,9 +39963,9 @@ async function ollamaList(globalOpts) {
|
|
|
38782
39963
|
modelCount: modelCounts.get(s.id) ?? 0
|
|
38783
39964
|
}));
|
|
38784
39965
|
output(data, (d) => {
|
|
38785
|
-
const
|
|
39966
|
+
const list2 = d;
|
|
38786
39967
|
const lines = ["", divider("Ollama Servers"), ""];
|
|
38787
|
-
for (const s of
|
|
39968
|
+
for (const s of list2) {
|
|
38788
39969
|
const dot = statusDot(s.status === "online" ? "active" : "offline");
|
|
38789
39970
|
lines.push(` ${dot} ${s.name} ${muted(`(${s.host}:${s.port})`)}`);
|
|
38790
39971
|
lines.push(` Models: ${s.modelCount} \xB7 Status: ${s.status === "online" ? success("online") : error2("offline")}`);
|
|
@@ -38936,13 +40117,13 @@ async function ollamaDiscover(globalOpts, opts) {
|
|
|
38936
40117
|
contextWindow: m.contextWindow,
|
|
38937
40118
|
sizeBytes: m.sizeBytes
|
|
38938
40119
|
})), (d) => {
|
|
38939
|
-
const
|
|
38940
|
-
if (
|
|
40120
|
+
const list2 = d;
|
|
40121
|
+
if (list2.length === 0) {
|
|
38941
40122
|
return "\n No models found. Check server connectivity.\n";
|
|
38942
40123
|
}
|
|
38943
40124
|
const lines = [`
|
|
38944
|
-
${success(`\u2713 Discovered ${
|
|
38945
|
-
for (const m of
|
|
40125
|
+
${success(`\u2713 Discovered ${list2.length} model(s):`)}`, ""];
|
|
40126
|
+
for (const m of list2) {
|
|
38946
40127
|
const sizeGB = m.sizeBytes > 0 ? `${(m.sizeBytes / 1e9).toFixed(1)}GB` : "";
|
|
38947
40128
|
const ctxK = m.contextWindow ? `${(m.contextWindow / 1e3).toFixed(0)}K ctx` : "";
|
|
38948
40129
|
const meta = [m.parameterSize, sizeGB, ctxK].filter(Boolean).join(" \xB7 ");
|
|
@@ -39029,9 +40210,9 @@ async function backendList(globalOpts) {
|
|
|
39029
40210
|
defaultModel: a.defaultModel
|
|
39030
40211
|
}));
|
|
39031
40212
|
output(data, (d) => {
|
|
39032
|
-
const
|
|
40213
|
+
const list2 = d;
|
|
39033
40214
|
const lines = ["", divider("Backends"), ""];
|
|
39034
|
-
for (const b of
|
|
40215
|
+
for (const b of list2) {
|
|
39035
40216
|
const marker = b.active ? success("\u25CF ") : " ";
|
|
39036
40217
|
lines.push(` ${marker}${b.displayName} (${b.id})${b.active ? success(" \u2190 active") : ""}`);
|
|
39037
40218
|
lines.push(` Default model: ${muted(b.defaultModel)}`);
|
|
@@ -39349,12 +40530,12 @@ async function cronList(globalOpts) {
|
|
|
39349
40530
|
const jobs = readDb.prepare("SELECT * FROM jobs ORDER BY id").all();
|
|
39350
40531
|
readDb.close();
|
|
39351
40532
|
output(jobs, (d) => {
|
|
39352
|
-
const
|
|
39353
|
-
if (
|
|
40533
|
+
const list2 = d;
|
|
40534
|
+
if (list2.length === 0) return `
|
|
39354
40535
|
${muted("No scheduled jobs.")}
|
|
39355
40536
|
`;
|
|
39356
|
-
const lines = ["", divider(`Scheduled Jobs (${
|
|
39357
|
-
for (const j of
|
|
40537
|
+
const lines = ["", divider(`Scheduled Jobs (${list2.length})`), ""];
|
|
40538
|
+
for (const j of list2) {
|
|
39358
40539
|
const status = !j.active ? "cancelled" : !j.enabled ? "paused" : "active";
|
|
39359
40540
|
const schedule2 = j.cron ?? (j.at_time ? `at ${j.at_time}` : j.every_ms ? `every ${j.every_ms / 1e3}s` : "?");
|
|
39360
40541
|
const tz = j.timezone !== "UTC" ? ` (${j.timezone})` : "";
|
|
@@ -39574,12 +40755,12 @@ async function cronRuns(globalOpts, jobId, opts) {
|
|
|
39574
40755
|
const runs = readDb.prepare(query).all(...params);
|
|
39575
40756
|
readDb.close();
|
|
39576
40757
|
output(runs, (d) => {
|
|
39577
|
-
const
|
|
39578
|
-
if (
|
|
40758
|
+
const list2 = d;
|
|
40759
|
+
if (list2.length === 0) return `
|
|
39579
40760
|
${muted(jobId ? `No runs for job #${jobId}.` : "No run history yet.")}
|
|
39580
40761
|
`;
|
|
39581
40762
|
const lines = ["", divider("Run History"), ""];
|
|
39582
|
-
for (const r of
|
|
40763
|
+
for (const r of list2) {
|
|
39583
40764
|
const duration = r.duration_ms ? ` (${(r.duration_ms / 1e3).toFixed(1)}s)` : "";
|
|
39584
40765
|
lines.push(` #${r.job_id} [${r.status}] ${formatLocalDateTime(r.started_at)}${duration}`);
|
|
39585
40766
|
if (r.error) lines.push(` Error: ${r.error.slice(0, 100)}`);
|
|
@@ -39622,12 +40803,12 @@ async function agentsList(globalOpts) {
|
|
|
39622
40803
|
).all();
|
|
39623
40804
|
readDb.close();
|
|
39624
40805
|
output(agents2, (d) => {
|
|
39625
|
-
const
|
|
39626
|
-
if (
|
|
40806
|
+
const list2 = d;
|
|
40807
|
+
if (list2.length === 0) return `
|
|
39627
40808
|
${muted("No active agents.")}
|
|
39628
40809
|
`;
|
|
39629
|
-
const lines = ["", divider(`Active Agents (${
|
|
39630
|
-
for (const a of
|
|
40810
|
+
const lines = ["", divider(`Active Agents (${list2.length})`), ""];
|
|
40811
|
+
for (const a of list2) {
|
|
39631
40812
|
const shortId = a.id?.slice(0, 8) ?? "?";
|
|
39632
40813
|
lines.push(` ${statusDot(a.status)} ${shortId} (${a.runnerId}) \u2014 ${a.status}`);
|
|
39633
40814
|
if (a.task) lines.push(` Task: ${a.task.slice(0, 80)}${a.task.length > 80 ? "\u2026" : ""}`);
|
|
@@ -39653,12 +40834,12 @@ async function tasksList(globalOpts) {
|
|
|
39653
40834
|
).all();
|
|
39654
40835
|
readDb.close();
|
|
39655
40836
|
output(tasks, (d) => {
|
|
39656
|
-
const
|
|
39657
|
-
if (
|
|
40837
|
+
const list2 = d;
|
|
40838
|
+
if (list2.length === 0) return `
|
|
39658
40839
|
${muted("No active tasks.")}
|
|
39659
40840
|
`;
|
|
39660
|
-
const lines = ["", divider(`Task Board (${
|
|
39661
|
-
for (const t of
|
|
40841
|
+
const lines = ["", divider(`Task Board (${list2.length})`), ""];
|
|
40842
|
+
for (const t of list2) {
|
|
39662
40843
|
const assignee = t.assignee ? ` (\u2192 ${t.assignee.slice(0, 8)})` : "";
|
|
39663
40844
|
lines.push(` ${statusDot(t.status === "completed" ? "ok" : t.status === "in_progress" ? "running" : "paused")} #${t.id}: ${t.subject}${assignee}`);
|
|
39664
40845
|
}
|
|
@@ -39737,12 +40918,12 @@ async function runnersList(globalOpts) {
|
|
|
39737
40918
|
displayName: r.displayName,
|
|
39738
40919
|
specialties: r.capabilities.specialties ?? []
|
|
39739
40920
|
})), (d) => {
|
|
39740
|
-
const
|
|
39741
|
-
if (
|
|
40921
|
+
const list2 = d;
|
|
40922
|
+
if (list2.length === 0) return `
|
|
39742
40923
|
${muted("No runners registered.")}
|
|
39743
40924
|
`;
|
|
39744
|
-
const lines = ["", divider(`Registered Runners (${
|
|
39745
|
-
for (const r of
|
|
40925
|
+
const lines = ["", divider(`Registered Runners (${list2.length})`), ""];
|
|
40926
|
+
for (const r of list2) {
|
|
39746
40927
|
const specs = r.specialties.length > 0 ? ` \u2014 ${r.specialties.join(", ")}` : "";
|
|
39747
40928
|
lines.push(` \u2022 ${r.id} (${r.displayName})${specs}`);
|
|
39748
40929
|
}
|
|
@@ -39965,9 +41146,9 @@ async function usageTokens(globalOpts) {
|
|
|
39965
41146
|
});
|
|
39966
41147
|
readDb.close();
|
|
39967
41148
|
output(data, (d) => {
|
|
39968
|
-
const
|
|
41149
|
+
const list2 = d;
|
|
39969
41150
|
const lines = ["", divider("Backend usage (last 24h)"), ""];
|
|
39970
|
-
for (const u of
|
|
41151
|
+
for (const u of list2) {
|
|
39971
41152
|
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
41153
|
}
|
|
39973
41154
|
lines.push("");
|
|
@@ -39981,13 +41162,13 @@ async function limitsList(globalOpts) {
|
|
|
39981
41162
|
const limits2 = readDb.prepare("SELECT * FROM backend_limits").all();
|
|
39982
41163
|
readDb.close();
|
|
39983
41164
|
output(limits2, (d) => {
|
|
39984
|
-
const
|
|
39985
|
-
if (
|
|
41165
|
+
const list2 = d;
|
|
41166
|
+
if (list2.length === 0) return `
|
|
39986
41167
|
${muted("No usage limits set.")}
|
|
39987
41168
|
${muted("Set with: cc-claw usage limits set <backend> <window> <tokens>")}
|
|
39988
41169
|
`;
|
|
39989
41170
|
const lines = ["", divider("Usage limits"), ""];
|
|
39990
|
-
for (const l of
|
|
41171
|
+
for (const l of list2) {
|
|
39991
41172
|
lines.push(` ${l.backend} (${l.window}): ${l.max_input_tokens ? `${(l.max_input_tokens / 1e3).toFixed(0)}K input tokens` : "no limit"}`);
|
|
39992
41173
|
}
|
|
39993
41174
|
lines.push("");
|
|
@@ -40295,9 +41476,9 @@ async function toolsList(globalOpts) {
|
|
|
40295
41476
|
const toolMap = new Map(rows.map((r) => [r.tool, !!r.enabled]));
|
|
40296
41477
|
const tools2 = ALL_TOOLS4.map((t) => ({ name: t, enabled: toolMap.get(t) ?? true }));
|
|
40297
41478
|
output(tools2, (d) => {
|
|
40298
|
-
const
|
|
41479
|
+
const list2 = d;
|
|
40299
41480
|
const lines = ["", divider("Tools"), ""];
|
|
40300
|
-
for (const t of
|
|
41481
|
+
for (const t of list2) {
|
|
40301
41482
|
lines.push(` ${checkMark(t.enabled)} ${t.name}`);
|
|
40302
41483
|
}
|
|
40303
41484
|
lines.push("");
|
|
@@ -40824,13 +42005,13 @@ async function chatsList(_globalOpts) {
|
|
|
40824
42005
|
const aliases = readDb.prepare("SELECT alias, chat_id FROM chat_aliases ORDER BY alias").all();
|
|
40825
42006
|
readDb.close();
|
|
40826
42007
|
output(aliases, (d) => {
|
|
40827
|
-
const
|
|
40828
|
-
if (
|
|
42008
|
+
const list2 = d;
|
|
42009
|
+
if (list2.length === 0) return `
|
|
40829
42010
|
${muted("No chat aliases configured yet.")}
|
|
40830
42011
|
${muted("Use: cc-claw chats alias <chat_id> <name>")}
|
|
40831
42012
|
`;
|
|
40832
42013
|
const lines = ["", divider("Chat Aliases"), ""];
|
|
40833
|
-
for (const a of
|
|
42014
|
+
for (const a of list2) {
|
|
40834
42015
|
lines.push(` ${a.alias} \u2192 ${a.chat_id}`);
|
|
40835
42016
|
}
|
|
40836
42017
|
lines.push("");
|
|
@@ -40893,13 +42074,13 @@ async function skillsList(_globalOpts) {
|
|
|
40893
42074
|
sources: s.sources,
|
|
40894
42075
|
filePath: s.filePath
|
|
40895
42076
|
})), (d) => {
|
|
40896
|
-
const
|
|
40897
|
-
if (
|
|
42077
|
+
const list2 = d;
|
|
42078
|
+
if (list2.length === 0) return `
|
|
40898
42079
|
${muted("No skills found.")}
|
|
40899
42080
|
${muted("Install with: cc-claw skills install <github-url>")}
|
|
40900
42081
|
`;
|
|
40901
|
-
const lines = ["", divider(`Skills (${
|
|
40902
|
-
for (const s of
|
|
42082
|
+
const lines = ["", divider(`Skills (${list2.length})`), ""];
|
|
42083
|
+
for (const s of list2) {
|
|
40903
42084
|
const tags = s.sources.join(", ");
|
|
40904
42085
|
const desc = s.description ? ` \u2014 ${s.description.slice(0, 60)}` : "";
|
|
40905
42086
|
lines.push(` \u2022 ${s.name} [${muted(tags)}]${desc}`);
|
|
@@ -40964,14 +42145,14 @@ async function mcpList(globalOpts) {
|
|
|
40964
42145
|
const mcps = listMcpServers2(db3);
|
|
40965
42146
|
db3.close();
|
|
40966
42147
|
output(mcps, (d) => {
|
|
40967
|
-
const
|
|
40968
|
-
if (
|
|
42148
|
+
const list2 = d;
|
|
42149
|
+
if (list2.length === 0) {
|
|
40969
42150
|
return `
|
|
40970
42151
|
${muted("No MCP servers registered. Use `cc-claw mcp add` or `cc-claw mcp import`.")}
|
|
40971
42152
|
`;
|
|
40972
42153
|
}
|
|
40973
|
-
const lines = ["", divider(`MCP Servers (${
|
|
40974
|
-
for (const m of
|
|
42154
|
+
const lines = ["", divider(`MCP Servers (${list2.length})`), ""];
|
|
42155
|
+
for (const m of list2) {
|
|
40975
42156
|
const lock = SYSTEM_MCP_NAMES.has(m.name) ? " \u{1F512}" : "";
|
|
40976
42157
|
const pin = m.enabledByDefault ? " \u{1F4CC}" : "";
|
|
40977
42158
|
const desc = m.description ? ` \u2014 ${muted(m.description)}` : "";
|
|
@@ -41378,6 +42559,7 @@ async function tuiCommand(globalOpts, cmdOpts) {
|
|
|
41378
42559
|
outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
|
|
41379
42560
|
process.exit(1);
|
|
41380
42561
|
}
|
|
42562
|
+
globalThis._cliStayAlive = true;
|
|
41381
42563
|
const chatId = resolveChatId2(globalOpts);
|
|
41382
42564
|
let theme = getTheme();
|
|
41383
42565
|
const rl2 = createInterface10({
|
|
@@ -43387,6 +44569,7 @@ program.command("council").alias("debate").description("Multi-model council deba
|
|
|
43387
44569
|
console.log("Select 2+ models, pose a question, and they debate anonymously for up to 3 rounds.");
|
|
43388
44570
|
});
|
|
43389
44571
|
program.command("start", { hidden: true }).description("Run the bot in the foreground (use 'service start' for background daemon)").action(async () => {
|
|
44572
|
+
globalThis._cliStayAlive = true;
|
|
43390
44573
|
await Promise.resolve().then(() => (init_index(), index_exports));
|
|
43391
44574
|
});
|
|
43392
44575
|
program.command("install", { hidden: true }).description("Install as background service \u2014 alias for service install").action(async () => {
|
|
@@ -43398,6 +44581,7 @@ program.command("uninstall", { hidden: true }).description("Remove background se
|
|
|
43398
44581
|
uninstallService2();
|
|
43399
44582
|
});
|
|
43400
44583
|
program.command("setup").description("Interactive configuration wizard").option("--dry-run", "Run the wizard without saving anything (demo/test mode)").action(async (opts) => {
|
|
44584
|
+
globalThis._cliStayAlive = true;
|
|
43401
44585
|
if (opts.dryRun) process.env.CC_CLAW_SETUP_DRY_RUN = "1";
|
|
43402
44586
|
await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
43403
44587
|
});
|
|
@@ -43433,10 +44617,12 @@ Update available: v${latest} (current: v${VERSION})`);
|
|
|
43433
44617
|
return;
|
|
43434
44618
|
}
|
|
43435
44619
|
await program.parseAsync(argv);
|
|
43436
|
-
if (
|
|
43437
|
-
process.stdout.
|
|
43438
|
-
|
|
43439
|
-
|
|
44620
|
+
if (!globalThis._cliStayAlive) {
|
|
44621
|
+
if (process.stdout.writableLength > 0) {
|
|
44622
|
+
process.stdout.once("drain", () => process.exit(0));
|
|
44623
|
+
} else {
|
|
44624
|
+
setTimeout(() => process.exit(0), 50);
|
|
44625
|
+
}
|
|
43440
44626
|
}
|
|
43441
44627
|
}
|
|
43442
44628
|
|