switchroom 0.21.1 → 0.21.4
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/bin/handoff-briefing.sh +23 -1
- package/dist/auth-broker/index.js +54 -34
- package/dist/cli/switchroom.js +840 -379
- package/dist/host-control/main.js +8 -1
- package/package.json +3 -2
- package/telegram-plugin/dist/gateway/gateway.js +378 -111
- package/telegram-plugin/external-spend.ts +1 -1
- package/telegram-plugin/gateway/gateway.ts +3 -1
- package/telegram-plugin/gateway/orphaned-db-sweep.ts +315 -0
- package/telegram-plugin/history.ts +328 -62
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +19 -4
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +8 -2
- package/telegram-plugin/tests/orphaned-db-sweep.test.ts +713 -0
|
@@ -30100,6 +30100,7 @@ var exports_history = {};
|
|
|
30100
30100
|
__export(exports_history, {
|
|
30101
30101
|
verifyHistoryWritable: () => verifyHistoryWritable2,
|
|
30102
30102
|
updateSystemOutboundText: () => updateSystemOutboundText2,
|
|
30103
|
+
reopenHistory: () => reopenHistory2,
|
|
30103
30104
|
recordSystemOutbound: () => recordSystemOutbound2,
|
|
30104
30105
|
recordReaction: () => recordReaction2,
|
|
30105
30106
|
recordOutbound: () => recordOutbound2,
|
|
@@ -30112,8 +30113,10 @@ __export(exports_history, {
|
|
|
30112
30113
|
initHistory: () => initHistory2,
|
|
30113
30114
|
hasOutboundWithText: () => hasOutboundWithText2,
|
|
30114
30115
|
hasOutboundDeliveredSince: () => hasOutboundDeliveredSince2,
|
|
30116
|
+
hardCloseDb: () => hardCloseDb2,
|
|
30115
30117
|
getRecentOutboundCount: () => getRecentOutboundCount,
|
|
30116
30118
|
getLatestInboundMessageId: () => getLatestInboundMessageId2,
|
|
30119
|
+
getHistoryReopenFailure: () => getHistoryReopenFailure2,
|
|
30117
30120
|
getHistoryDbForBriefing: () => getHistoryDbForBriefing,
|
|
30118
30121
|
deliveryTextMatch: () => deliveryTextMatch2,
|
|
30119
30122
|
deleteFromHistory: () => deleteFromHistory2,
|
|
@@ -30140,6 +30143,32 @@ function loadDatabaseClass2() {
|
|
|
30140
30143
|
throw new Error(`history.ts requires Bun runtime (bun:sqlite). Caller: ${err.message}`);
|
|
30141
30144
|
}
|
|
30142
30145
|
}
|
|
30146
|
+
function prep2(sql) {
|
|
30147
|
+
const cached = stmtCache2.get(sql);
|
|
30148
|
+
if (cached != null)
|
|
30149
|
+
return cached;
|
|
30150
|
+
const stmt = requireDb2().prepare(sql);
|
|
30151
|
+
if (stmtCache2.size >= MAX_CACHED_STATEMENTS2) {
|
|
30152
|
+
const oldestKey = stmtCache2.keys().next().value;
|
|
30153
|
+
if (oldestKey != null) {
|
|
30154
|
+
const oldest = stmtCache2.get(oldestKey);
|
|
30155
|
+
stmtCache2.delete(oldestKey);
|
|
30156
|
+
try {
|
|
30157
|
+
oldest?.finalize?.();
|
|
30158
|
+
} catch {}
|
|
30159
|
+
}
|
|
30160
|
+
}
|
|
30161
|
+
stmtCache2.set(sql, stmt);
|
|
30162
|
+
return stmt;
|
|
30163
|
+
}
|
|
30164
|
+
function finalizeCachedStatements2() {
|
|
30165
|
+
for (const stmt of stmtCache2.values()) {
|
|
30166
|
+
try {
|
|
30167
|
+
stmt.finalize?.();
|
|
30168
|
+
} catch {}
|
|
30169
|
+
}
|
|
30170
|
+
stmtCache2.clear();
|
|
30171
|
+
}
|
|
30143
30172
|
function warnHistory2(msg) {
|
|
30144
30173
|
try {
|
|
30145
30174
|
process.stderr.write(`telegram history: ${msg}
|
|
@@ -30202,7 +30231,7 @@ function initHistory2(stateDir, retentionDays = 30) {
|
|
|
30202
30231
|
}
|
|
30203
30232
|
}
|
|
30204
30233
|
const LOGICAL_KEY_INDEX = "idx_messages_logical_key";
|
|
30205
|
-
const logicalKeyIndexExists =
|
|
30234
|
+
const logicalKeyIndexExists = prep2(`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ?`).get(LOGICAL_KEY_INDEX) != null;
|
|
30206
30235
|
if (!logicalKeyIndexExists) {
|
|
30207
30236
|
db2.exec(`
|
|
30208
30237
|
DELETE FROM messages
|
|
@@ -30231,7 +30260,7 @@ function initHistory2(stateDir, retentionDays = 30) {
|
|
|
30231
30260
|
adoptSqliteOwnership(path2);
|
|
30232
30261
|
if (retentionDays > 0) {
|
|
30233
30262
|
const cutoff = Math.floor(Date.now() / 1000) - retentionDays * 86400;
|
|
30234
|
-
|
|
30263
|
+
prep2("DELETE FROM messages WHERE ts < ?").run(cutoff);
|
|
30235
30264
|
}
|
|
30236
30265
|
const check = verifyHistoryWritable2();
|
|
30237
30266
|
if (!check.ok) {
|
|
@@ -30244,11 +30273,11 @@ function verifyHistoryWritable2() {
|
|
|
30244
30273
|
const SENTINEL_CHAT = "__history_selfcheck__";
|
|
30245
30274
|
const sentinelId = Date.now();
|
|
30246
30275
|
try {
|
|
30247
|
-
|
|
30248
|
-
|
|
30276
|
+
prep2("DELETE FROM messages WHERE chat_id = ?").run(SENTINEL_CHAT);
|
|
30277
|
+
prep2(`INSERT OR REPLACE INTO messages
|
|
30249
30278
|
(chat_id, thread_id, message_id, role, ts, text)
|
|
30250
30279
|
VALUES (?, NULL, ?, 'assistant', ?, ?)`).run(SENTINEL_CHAT, sentinelId, Math.floor(Date.now() / 1000), "selfcheck");
|
|
30251
|
-
const row =
|
|
30280
|
+
const row = prep2("SELECT text FROM messages WHERE chat_id = ? AND message_id = ?").get(SENTINEL_CHAT, sentinelId);
|
|
30252
30281
|
if (row?.text !== "selfcheck") {
|
|
30253
30282
|
return { ok: false, error: "sentinel row not read back after insert" };
|
|
30254
30283
|
}
|
|
@@ -30257,24 +30286,32 @@ function verifyHistoryWritable2() {
|
|
|
30257
30286
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
30258
30287
|
} finally {
|
|
30259
30288
|
try {
|
|
30260
|
-
|
|
30289
|
+
prep2("DELETE FROM messages WHERE chat_id = ?").run(SENTINEL_CHAT);
|
|
30261
30290
|
} catch {}
|
|
30262
30291
|
}
|
|
30263
30292
|
}
|
|
30264
30293
|
function getHistoryDbForBriefing() {
|
|
30265
|
-
|
|
30294
|
+
if (db2 == null)
|
|
30295
|
+
return null;
|
|
30296
|
+
return {
|
|
30297
|
+
prepare(sql) {
|
|
30298
|
+
return { all: (...params) => prep2(sql).all(...params) };
|
|
30299
|
+
}
|
|
30300
|
+
};
|
|
30266
30301
|
}
|
|
30267
30302
|
function _resetForTests() {
|
|
30303
|
+
historyReopenFailure2 = null;
|
|
30268
30304
|
if (db2 != null) {
|
|
30269
|
-
db2
|
|
30305
|
+
const current = db2;
|
|
30270
30306
|
db2 = null;
|
|
30307
|
+
hardCloseDb2(current);
|
|
30271
30308
|
}
|
|
30272
30309
|
}
|
|
30273
30310
|
function checkpointWal2() {
|
|
30274
30311
|
if (db2 == null)
|
|
30275
30312
|
return false;
|
|
30276
30313
|
try {
|
|
30277
|
-
|
|
30314
|
+
prep2("PRAGMA wal_checkpoint(TRUNCATE)").run();
|
|
30278
30315
|
if (dbPath2) {
|
|
30279
30316
|
for (const suffix of ["-shm", "-wal"]) {
|
|
30280
30317
|
const f = dbPath2 + suffix;
|
|
@@ -30291,13 +30328,44 @@ function checkpointWal2() {
|
|
|
30291
30328
|
return false;
|
|
30292
30329
|
}
|
|
30293
30330
|
}
|
|
30331
|
+
function reopenHistory2(stateDir, retentionDays = 30) {
|
|
30332
|
+
const current = db2;
|
|
30333
|
+
if (current != null) {
|
|
30334
|
+
hardCloseDb2(current);
|
|
30335
|
+
}
|
|
30336
|
+
db2 = null;
|
|
30337
|
+
try {
|
|
30338
|
+
initHistory2(stateDir, retentionDays);
|
|
30339
|
+
const check = verifyHistoryWritable2();
|
|
30340
|
+
if (!check.ok) {
|
|
30341
|
+
throw new Error(`post-reopen writer self-check failed: ${check.error ?? "unknown"}`);
|
|
30342
|
+
}
|
|
30343
|
+
} catch (err) {
|
|
30344
|
+
historyReopenFailure2 = err instanceof Error ? err.message : String(err);
|
|
30345
|
+
throw err;
|
|
30346
|
+
}
|
|
30347
|
+
historyReopenFailure2 = null;
|
|
30348
|
+
}
|
|
30349
|
+
function hardCloseDb2(handle) {
|
|
30350
|
+
finalizeCachedStatements2();
|
|
30351
|
+
const gc = globalThis.Bun?.gc;
|
|
30352
|
+
if (typeof gc === "function") {
|
|
30353
|
+
try {
|
|
30354
|
+
gc(true);
|
|
30355
|
+
} catch {}
|
|
30356
|
+
}
|
|
30357
|
+
handle.close(true);
|
|
30358
|
+
}
|
|
30359
|
+
function getHistoryReopenFailure2() {
|
|
30360
|
+
return historyReopenFailure2;
|
|
30361
|
+
}
|
|
30294
30362
|
function pruneMessagesOlderThanDays2(retentionDays, nowSec, batchLimit = 5000) {
|
|
30295
30363
|
if (db2 == null)
|
|
30296
30364
|
return 0;
|
|
30297
30365
|
if (retentionDays <= 0)
|
|
30298
30366
|
return 0;
|
|
30299
30367
|
const cutoffSec = (nowSec ?? Math.floor(Date.now() / 1000)) - retentionDays * 86400;
|
|
30300
|
-
const stmt =
|
|
30368
|
+
const stmt = prep2(`
|
|
30301
30369
|
DELETE FROM messages
|
|
30302
30370
|
WHERE rowid IN (
|
|
30303
30371
|
SELECT rowid FROM messages WHERE ts < ? LIMIT ?
|
|
@@ -30326,7 +30394,7 @@ function recordInbound2(args) {
|
|
|
30326
30394
|
warnHistory2(`recordInbound: dropping row with invalid message_id=${String(args.message_id)} ` + `(chat=${args.chat_id}) \u2014 a delivered inbound will be absent from history`);
|
|
30327
30395
|
return;
|
|
30328
30396
|
}
|
|
30329
|
-
const stmt =
|
|
30397
|
+
const stmt = prep2(`
|
|
30330
30398
|
INSERT OR REPLACE INTO messages
|
|
30331
30399
|
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id, reply_to_message_id, reply_to_text, forwarded_from, forwarded_from_type, forwarded_from_id, forwarded_date, forwarded_message_id)
|
|
30332
30400
|
VALUES (?, ?, ?, 'user', ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -30353,12 +30421,12 @@ function recordOutbound2(args) {
|
|
|
30353
30421
|
if (validRows.length === 0)
|
|
30354
30422
|
return;
|
|
30355
30423
|
const groupId = validRows[0].id;
|
|
30356
|
-
const stmt =
|
|
30424
|
+
const stmt = prep2(`
|
|
30357
30425
|
INSERT OR REPLACE INTO messages
|
|
30358
30426
|
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id)
|
|
30359
30427
|
VALUES (?, ?, ?, 'assistant', NULL, NULL, ?, ?, ?, ?)
|
|
30360
30428
|
`);
|
|
30361
|
-
const dropSystem =
|
|
30429
|
+
const dropSystem = prep2(`DELETE FROM messages WHERE chat_id = ? AND message_id = ? AND role = 'system'`);
|
|
30362
30430
|
const tx = requireDb2().transaction((rows) => {
|
|
30363
30431
|
for (const r of rows) {
|
|
30364
30432
|
dropSystem.run(args.chat_id, r.id);
|
|
@@ -30378,8 +30446,8 @@ function recordSystemOutbound2(args) {
|
|
|
30378
30446
|
if (db2 == null)
|
|
30379
30447
|
return false;
|
|
30380
30448
|
try {
|
|
30381
|
-
const res =
|
|
30382
|
-
|
|
30449
|
+
const res = prep2(`
|
|
30450
|
+
INSERT INTO messages
|
|
30383
30451
|
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id, kind)
|
|
30384
30452
|
SELECT ?, ?, ?, 'system', NULL, NULL, ?, ?, NULL, NULL, ?
|
|
30385
30453
|
WHERE NOT EXISTS (
|
|
@@ -30394,31 +30462,31 @@ function recordSystemOutbound2(args) {
|
|
|
30394
30462
|
}
|
|
30395
30463
|
function updateSystemOutboundText2(args) {
|
|
30396
30464
|
try {
|
|
30397
|
-
const res =
|
|
30465
|
+
const res = prep2(`UPDATE messages SET text = ? WHERE chat_id = ? AND message_id = ? AND role = 'system'`).run(redact(args.text), args.chat_id, args.message_id);
|
|
30398
30466
|
return (res?.changes ?? 0) > 0;
|
|
30399
30467
|
} catch {
|
|
30400
30468
|
return false;
|
|
30401
30469
|
}
|
|
30402
30470
|
}
|
|
30403
30471
|
function recordEdit2(args) {
|
|
30404
|
-
|
|
30405
|
-
|
|
30406
|
-
|
|
30407
|
-
|
|
30408
|
-
|
|
30472
|
+
prep2(`
|
|
30473
|
+
UPDATE messages
|
|
30474
|
+
SET text = ?
|
|
30475
|
+
WHERE chat_id = ? AND message_id = ?
|
|
30476
|
+
`).run(redact(args.text), args.chat_id, args.message_id);
|
|
30409
30477
|
}
|
|
30410
30478
|
function recordReaction2(args) {
|
|
30411
|
-
|
|
30412
|
-
|
|
30413
|
-
|
|
30414
|
-
|
|
30415
|
-
|
|
30479
|
+
prep2(`
|
|
30480
|
+
UPDATE messages
|
|
30481
|
+
SET user_reaction = ?
|
|
30482
|
+
WHERE chat_id = ? AND message_id = ?
|
|
30483
|
+
`).run(args.emoji, args.chat_id, args.message_id);
|
|
30416
30484
|
}
|
|
30417
30485
|
function deleteFromHistory2(args) {
|
|
30418
|
-
|
|
30419
|
-
|
|
30420
|
-
|
|
30421
|
-
|
|
30486
|
+
prep2(`
|
|
30487
|
+
DELETE FROM messages
|
|
30488
|
+
WHERE chat_id = ? AND message_id = ?
|
|
30489
|
+
`).run(args.chat_id, args.message_id);
|
|
30422
30490
|
}
|
|
30423
30491
|
function getLatestInboundMessageId2(chatId, threadId) {
|
|
30424
30492
|
const params = [chatId];
|
|
@@ -30432,19 +30500,21 @@ function getLatestInboundMessageId2(chatId, threadId) {
|
|
|
30432
30500
|
}
|
|
30433
30501
|
}
|
|
30434
30502
|
sql += " ORDER BY ts DESC, message_id DESC LIMIT 1";
|
|
30435
|
-
const row =
|
|
30503
|
+
const row = prep2(sql).get(...params);
|
|
30436
30504
|
return row?.message_id ?? null;
|
|
30437
30505
|
}
|
|
30438
30506
|
function lookupMessageRoleAndText2(chatId, messageId, opts) {
|
|
30507
|
+
if (db2 == null)
|
|
30508
|
+
return null;
|
|
30439
30509
|
const sql = `SELECT role, text, kind FROM messages WHERE chat_id = ? AND message_id = ?` + (opts?.includeSystem === true ? "" : ` AND role <> 'system'`) + ` LIMIT 1`;
|
|
30440
|
-
const row =
|
|
30510
|
+
const row = prep2(sql).get(chatId, messageId);
|
|
30441
30511
|
if (!row)
|
|
30442
30512
|
return null;
|
|
30443
30513
|
return { role: row.role, text: row.text ?? "", kind: row.kind ?? null };
|
|
30444
30514
|
}
|
|
30445
30515
|
function getRecentOutboundCount(chatId, withinSeconds) {
|
|
30446
30516
|
const cutoff = Math.floor(Date.now() / 1000) - withinSeconds;
|
|
30447
|
-
const row =
|
|
30517
|
+
const row = prep2("SELECT COUNT(*) as cnt FROM messages WHERE chat_id = ? AND role = ? AND ts >= ?").get(chatId, "assistant", cutoff);
|
|
30448
30518
|
return row?.cnt ?? 0;
|
|
30449
30519
|
}
|
|
30450
30520
|
function hasOutboundDeliveredSince2(chatId, sinceMs, threadId, minChars = 200) {
|
|
@@ -30462,7 +30532,7 @@ function hasOutboundDeliveredSince2(chatId, sinceMs, threadId, minChars = 200) {
|
|
|
30462
30532
|
}
|
|
30463
30533
|
}
|
|
30464
30534
|
sql += " LIMIT 1";
|
|
30465
|
-
const row =
|
|
30535
|
+
const row = prep2(sql).get(...params);
|
|
30466
30536
|
return row != null;
|
|
30467
30537
|
} catch {
|
|
30468
30538
|
return false;
|
|
@@ -30488,7 +30558,7 @@ function hasOutboundWithText2(chatId, text4, threadId, sinceMs) {
|
|
|
30488
30558
|
params.push(Math.floor(sinceMs / 1000));
|
|
30489
30559
|
}
|
|
30490
30560
|
sql += " ORDER BY ts DESC LIMIT 500";
|
|
30491
|
-
const rows =
|
|
30561
|
+
const rows = prep2(sql).all(...params);
|
|
30492
30562
|
for (const r of rows) {
|
|
30493
30563
|
const hay = normalizeDeliveryText2(r.text ?? "");
|
|
30494
30564
|
if (hay.length === 0)
|
|
@@ -30513,6 +30583,10 @@ function deliveryTextMatch2(hay, needle) {
|
|
|
30513
30583
|
return hay.startsWith(needle) || needle.startsWith(hay);
|
|
30514
30584
|
}
|
|
30515
30585
|
function query2(opts) {
|
|
30586
|
+
if (db2 == null) {
|
|
30587
|
+
warnHistory2("query: history DB is not open \u2014 returning no rows. If a reopen failed, the " + "orphaned-db-sweep is alarming about it every tick; RESTART the gateway.");
|
|
30588
|
+
return [];
|
|
30589
|
+
}
|
|
30516
30590
|
const limit = Math.min(MAX_LIMIT2, Math.max(1, opts.limit ?? DEFAULT_LIMIT2));
|
|
30517
30591
|
const params = [opts.chat_id];
|
|
30518
30592
|
let sql = "SELECT * FROM messages WHERE chat_id = ?";
|
|
@@ -30532,14 +30606,15 @@ function query2(opts) {
|
|
|
30532
30606
|
}
|
|
30533
30607
|
sql += " ORDER BY ts DESC, message_id DESC LIMIT ?";
|
|
30534
30608
|
params.push(limit);
|
|
30535
|
-
const rows =
|
|
30609
|
+
const rows = prep2(sql).all(...params);
|
|
30536
30610
|
rows.reverse();
|
|
30537
30611
|
return rows;
|
|
30538
30612
|
}
|
|
30539
|
-
var DatabaseClass2 = null, DEFAULT_LIMIT2 = 10, MAX_LIMIT2 = 50, db2 = null, dbPath2 = null, MIN_PREFIX_MATCH_CHARS2 = 40;
|
|
30613
|
+
var DatabaseClass2 = null, DEFAULT_LIMIT2 = 10, MAX_LIMIT2 = 50, db2 = null, dbPath2 = null, stmtCache2, MAX_CACHED_STATEMENTS2 = 128, historyReopenFailure2 = null, MIN_PREFIX_MATCH_CHARS2 = 40;
|
|
30540
30614
|
var init_history = __esm(() => {
|
|
30541
30615
|
init_state_owner();
|
|
30542
30616
|
init_redact();
|
|
30617
|
+
stmtCache2 = new Map;
|
|
30543
30618
|
});
|
|
30544
30619
|
|
|
30545
30620
|
// gateway/turn-flush-suppression.ts
|
|
@@ -39117,15 +39192,24 @@ function summarizeExternalSpend(days, now = new Date) {
|
|
|
39117
39192
|
const top = Object.entries(byModel).filter(([, usd]) => usd > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, EXTERNAL_SPEND_TOP_N).map(([name, usd]) => ({ label: shortModelLabel(name), usd }));
|
|
39118
39193
|
return { day24hUsd, day7dUsd, top };
|
|
39119
39194
|
}
|
|
39120
|
-
function
|
|
39121
|
-
|
|
39122
|
-
|
|
39123
|
-
|
|
39124
|
-
|
|
39125
|
-
|
|
39126
|
-
|
|
39195
|
+
function normalizeDailyActivityRows(body) {
|
|
39196
|
+
const results = body && typeof body === "object" && Array.isArray(body.results) ? body.results : [];
|
|
39197
|
+
const rows = [];
|
|
39198
|
+
for (const day of results) {
|
|
39199
|
+
if (!day || typeof day !== "object")
|
|
39200
|
+
continue;
|
|
39201
|
+
const models = {};
|
|
39202
|
+
const breakdown = day.breakdown?.models ?? {};
|
|
39203
|
+
for (const [name, entry] of Object.entries(breakdown)) {
|
|
39204
|
+
const raw = entry?.metrics?.spend;
|
|
39205
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
39206
|
+
if (!Number.isFinite(n))
|
|
39207
|
+
continue;
|
|
39208
|
+
models[name] = (models[name] ?? 0) + n;
|
|
39209
|
+
}
|
|
39210
|
+
rows.push({ startTime: day.date, models });
|
|
39127
39211
|
}
|
|
39128
|
-
return
|
|
39212
|
+
return rows;
|
|
39129
39213
|
}
|
|
39130
39214
|
var EXTERNAL_SPEND_TOP_N = 3, EXTERNAL_SPEND_CACHE_TTL_MS = 90000, BARE_EXTERNAL_NEEDLES;
|
|
39131
39215
|
var init_external_spend = __esm(() => {
|
|
@@ -39341,7 +39425,7 @@ __export(exports_external_spend, {
|
|
|
39341
39425
|
utcDateString: () => utcDateString,
|
|
39342
39426
|
summarizeExternalSpend: () => summarizeExternalSpend,
|
|
39343
39427
|
shortModelLabel: () => shortModelLabel,
|
|
39344
|
-
|
|
39428
|
+
normalizeDailyActivityRows: () => normalizeDailyActivityRows,
|
|
39345
39429
|
isExternalModel: () => isExternalModel,
|
|
39346
39430
|
formatUsd: () => formatUsd,
|
|
39347
39431
|
formatExternalSpendBlock: () => formatExternalSpendBlock2,
|
|
@@ -39732,7 +39816,7 @@ import {
|
|
|
39732
39816
|
rmSync as rmSync9,
|
|
39733
39817
|
statSync as statSync25,
|
|
39734
39818
|
renameSync as renameSync30,
|
|
39735
|
-
realpathSync as
|
|
39819
|
+
realpathSync as realpathSync6,
|
|
39736
39820
|
chmodSync as chmodSync14,
|
|
39737
39821
|
openSync as openSync18,
|
|
39738
39822
|
closeSync as closeSync18,
|
|
@@ -39755,7 +39839,7 @@ function fsyncPathSync(path) {
|
|
|
39755
39839
|
|
|
39756
39840
|
// gateway/gateway.ts
|
|
39757
39841
|
import { homedir as homedir22 } from "os";
|
|
39758
|
-
import { join as join76, sep as sep6, basename as
|
|
39842
|
+
import { join as join76, sep as sep6, basename as basename18 } from "path";
|
|
39759
39843
|
|
|
39760
39844
|
// plugin-logger.ts
|
|
39761
39845
|
import { appendFileSync, mkdirSync, renameSync as renameSync2, statSync, existsSync } from "fs";
|
|
@@ -76082,6 +76166,34 @@ var DEFAULT_LIMIT = 10;
|
|
|
76082
76166
|
var MAX_LIMIT = 50;
|
|
76083
76167
|
var db = null;
|
|
76084
76168
|
var dbPath = null;
|
|
76169
|
+
var stmtCache = new Map;
|
|
76170
|
+
var MAX_CACHED_STATEMENTS = 128;
|
|
76171
|
+
function prep(sql) {
|
|
76172
|
+
const cached = stmtCache.get(sql);
|
|
76173
|
+
if (cached != null)
|
|
76174
|
+
return cached;
|
|
76175
|
+
const stmt = requireDb().prepare(sql);
|
|
76176
|
+
if (stmtCache.size >= MAX_CACHED_STATEMENTS) {
|
|
76177
|
+
const oldestKey = stmtCache.keys().next().value;
|
|
76178
|
+
if (oldestKey != null) {
|
|
76179
|
+
const oldest = stmtCache.get(oldestKey);
|
|
76180
|
+
stmtCache.delete(oldestKey);
|
|
76181
|
+
try {
|
|
76182
|
+
oldest?.finalize?.();
|
|
76183
|
+
} catch {}
|
|
76184
|
+
}
|
|
76185
|
+
}
|
|
76186
|
+
stmtCache.set(sql, stmt);
|
|
76187
|
+
return stmt;
|
|
76188
|
+
}
|
|
76189
|
+
function finalizeCachedStatements() {
|
|
76190
|
+
for (const stmt of stmtCache.values()) {
|
|
76191
|
+
try {
|
|
76192
|
+
stmt.finalize?.();
|
|
76193
|
+
} catch {}
|
|
76194
|
+
}
|
|
76195
|
+
stmtCache.clear();
|
|
76196
|
+
}
|
|
76085
76197
|
function warnHistory(msg) {
|
|
76086
76198
|
try {
|
|
76087
76199
|
process.stderr.write(`telegram history: ${msg}
|
|
@@ -76144,7 +76256,7 @@ function initHistory(stateDir, retentionDays = 30) {
|
|
|
76144
76256
|
}
|
|
76145
76257
|
}
|
|
76146
76258
|
const LOGICAL_KEY_INDEX = "idx_messages_logical_key";
|
|
76147
|
-
const logicalKeyIndexExists =
|
|
76259
|
+
const logicalKeyIndexExists = prep(`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ?`).get(LOGICAL_KEY_INDEX) != null;
|
|
76148
76260
|
if (!logicalKeyIndexExists) {
|
|
76149
76261
|
db.exec(`
|
|
76150
76262
|
DELETE FROM messages
|
|
@@ -76173,7 +76285,7 @@ function initHistory(stateDir, retentionDays = 30) {
|
|
|
76173
76285
|
adoptSqliteOwnership(path2);
|
|
76174
76286
|
if (retentionDays > 0) {
|
|
76175
76287
|
const cutoff = Math.floor(Date.now() / 1000) - retentionDays * 86400;
|
|
76176
|
-
|
|
76288
|
+
prep("DELETE FROM messages WHERE ts < ?").run(cutoff);
|
|
76177
76289
|
}
|
|
76178
76290
|
const check = verifyHistoryWritable();
|
|
76179
76291
|
if (!check.ok) {
|
|
@@ -76186,11 +76298,11 @@ function verifyHistoryWritable() {
|
|
|
76186
76298
|
const SENTINEL_CHAT = "__history_selfcheck__";
|
|
76187
76299
|
const sentinelId = Date.now();
|
|
76188
76300
|
try {
|
|
76189
|
-
|
|
76190
|
-
|
|
76301
|
+
prep("DELETE FROM messages WHERE chat_id = ?").run(SENTINEL_CHAT);
|
|
76302
|
+
prep(`INSERT OR REPLACE INTO messages
|
|
76191
76303
|
(chat_id, thread_id, message_id, role, ts, text)
|
|
76192
76304
|
VALUES (?, NULL, ?, 'assistant', ?, ?)`).run(SENTINEL_CHAT, sentinelId, Math.floor(Date.now() / 1000), "selfcheck");
|
|
76193
|
-
const row =
|
|
76305
|
+
const row = prep("SELECT text FROM messages WHERE chat_id = ? AND message_id = ?").get(SENTINEL_CHAT, sentinelId);
|
|
76194
76306
|
if (row?.text !== "selfcheck") {
|
|
76195
76307
|
return { ok: false, error: "sentinel row not read back after insert" };
|
|
76196
76308
|
}
|
|
@@ -76199,7 +76311,7 @@ function verifyHistoryWritable() {
|
|
|
76199
76311
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
76200
76312
|
} finally {
|
|
76201
76313
|
try {
|
|
76202
|
-
|
|
76314
|
+
prep("DELETE FROM messages WHERE chat_id = ?").run(SENTINEL_CHAT);
|
|
76203
76315
|
} catch {}
|
|
76204
76316
|
}
|
|
76205
76317
|
}
|
|
@@ -76207,7 +76319,7 @@ function checkpointWal() {
|
|
|
76207
76319
|
if (db == null)
|
|
76208
76320
|
return false;
|
|
76209
76321
|
try {
|
|
76210
|
-
|
|
76322
|
+
prep("PRAGMA wal_checkpoint(TRUNCATE)").run();
|
|
76211
76323
|
if (dbPath) {
|
|
76212
76324
|
for (const suffix of ["-shm", "-wal"]) {
|
|
76213
76325
|
const f = dbPath + suffix;
|
|
@@ -76224,13 +76336,45 @@ function checkpointWal() {
|
|
|
76224
76336
|
return false;
|
|
76225
76337
|
}
|
|
76226
76338
|
}
|
|
76339
|
+
function reopenHistory(stateDir, retentionDays = 30) {
|
|
76340
|
+
const current = db;
|
|
76341
|
+
if (current != null) {
|
|
76342
|
+
hardCloseDb(current);
|
|
76343
|
+
}
|
|
76344
|
+
db = null;
|
|
76345
|
+
try {
|
|
76346
|
+
initHistory(stateDir, retentionDays);
|
|
76347
|
+
const check = verifyHistoryWritable();
|
|
76348
|
+
if (!check.ok) {
|
|
76349
|
+
throw new Error(`post-reopen writer self-check failed: ${check.error ?? "unknown"}`);
|
|
76350
|
+
}
|
|
76351
|
+
} catch (err) {
|
|
76352
|
+
historyReopenFailure = err instanceof Error ? err.message : String(err);
|
|
76353
|
+
throw err;
|
|
76354
|
+
}
|
|
76355
|
+
historyReopenFailure = null;
|
|
76356
|
+
}
|
|
76357
|
+
function hardCloseDb(handle) {
|
|
76358
|
+
finalizeCachedStatements();
|
|
76359
|
+
const gc = globalThis.Bun?.gc;
|
|
76360
|
+
if (typeof gc === "function") {
|
|
76361
|
+
try {
|
|
76362
|
+
gc(true);
|
|
76363
|
+
} catch {}
|
|
76364
|
+
}
|
|
76365
|
+
handle.close(true);
|
|
76366
|
+
}
|
|
76367
|
+
var historyReopenFailure = null;
|
|
76368
|
+
function getHistoryReopenFailure() {
|
|
76369
|
+
return historyReopenFailure;
|
|
76370
|
+
}
|
|
76227
76371
|
function pruneMessagesOlderThanDays(retentionDays, nowSec, batchLimit = 5000) {
|
|
76228
76372
|
if (db == null)
|
|
76229
76373
|
return 0;
|
|
76230
76374
|
if (retentionDays <= 0)
|
|
76231
76375
|
return 0;
|
|
76232
76376
|
const cutoffSec = (nowSec ?? Math.floor(Date.now() / 1000)) - retentionDays * 86400;
|
|
76233
|
-
const stmt =
|
|
76377
|
+
const stmt = prep(`
|
|
76234
76378
|
DELETE FROM messages
|
|
76235
76379
|
WHERE rowid IN (
|
|
76236
76380
|
SELECT rowid FROM messages WHERE ts < ? LIMIT ?
|
|
@@ -76259,7 +76403,7 @@ function recordInbound(args) {
|
|
|
76259
76403
|
warnHistory(`recordInbound: dropping row with invalid message_id=${String(args.message_id)} ` + `(chat=${args.chat_id}) \u2014 a delivered inbound will be absent from history`);
|
|
76260
76404
|
return;
|
|
76261
76405
|
}
|
|
76262
|
-
const stmt =
|
|
76406
|
+
const stmt = prep(`
|
|
76263
76407
|
INSERT OR REPLACE INTO messages
|
|
76264
76408
|
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id, reply_to_message_id, reply_to_text, forwarded_from, forwarded_from_type, forwarded_from_id, forwarded_date, forwarded_message_id)
|
|
76265
76409
|
VALUES (?, ?, ?, 'user', ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -76286,12 +76430,12 @@ function recordOutbound(args) {
|
|
|
76286
76430
|
if (validRows.length === 0)
|
|
76287
76431
|
return;
|
|
76288
76432
|
const groupId = validRows[0].id;
|
|
76289
|
-
const stmt =
|
|
76433
|
+
const stmt = prep(`
|
|
76290
76434
|
INSERT OR REPLACE INTO messages
|
|
76291
76435
|
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id)
|
|
76292
76436
|
VALUES (?, ?, ?, 'assistant', NULL, NULL, ?, ?, ?, ?)
|
|
76293
76437
|
`);
|
|
76294
|
-
const dropSystem =
|
|
76438
|
+
const dropSystem = prep(`DELETE FROM messages WHERE chat_id = ? AND message_id = ? AND role = 'system'`);
|
|
76295
76439
|
const tx = requireDb().transaction((rows) => {
|
|
76296
76440
|
for (const r of rows) {
|
|
76297
76441
|
dropSystem.run(args.chat_id, r.id);
|
|
@@ -76311,8 +76455,8 @@ function recordSystemOutbound(args) {
|
|
|
76311
76455
|
if (db == null)
|
|
76312
76456
|
return false;
|
|
76313
76457
|
try {
|
|
76314
|
-
const res =
|
|
76315
|
-
|
|
76458
|
+
const res = prep(`
|
|
76459
|
+
INSERT INTO messages
|
|
76316
76460
|
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id, kind)
|
|
76317
76461
|
SELECT ?, ?, ?, 'system', NULL, NULL, ?, ?, NULL, NULL, ?
|
|
76318
76462
|
WHERE NOT EXISTS (
|
|
@@ -76327,31 +76471,31 @@ function recordSystemOutbound(args) {
|
|
|
76327
76471
|
}
|
|
76328
76472
|
function updateSystemOutboundText(args) {
|
|
76329
76473
|
try {
|
|
76330
|
-
const res =
|
|
76474
|
+
const res = prep(`UPDATE messages SET text = ? WHERE chat_id = ? AND message_id = ? AND role = 'system'`).run(redact(args.text), args.chat_id, args.message_id);
|
|
76331
76475
|
return (res?.changes ?? 0) > 0;
|
|
76332
76476
|
} catch {
|
|
76333
76477
|
return false;
|
|
76334
76478
|
}
|
|
76335
76479
|
}
|
|
76336
76480
|
function recordEdit(args) {
|
|
76337
|
-
|
|
76338
|
-
|
|
76339
|
-
|
|
76340
|
-
|
|
76341
|
-
|
|
76481
|
+
prep(`
|
|
76482
|
+
UPDATE messages
|
|
76483
|
+
SET text = ?
|
|
76484
|
+
WHERE chat_id = ? AND message_id = ?
|
|
76485
|
+
`).run(redact(args.text), args.chat_id, args.message_id);
|
|
76342
76486
|
}
|
|
76343
76487
|
function recordReaction(args) {
|
|
76344
|
-
|
|
76345
|
-
|
|
76346
|
-
|
|
76347
|
-
|
|
76348
|
-
|
|
76488
|
+
prep(`
|
|
76489
|
+
UPDATE messages
|
|
76490
|
+
SET user_reaction = ?
|
|
76491
|
+
WHERE chat_id = ? AND message_id = ?
|
|
76492
|
+
`).run(args.emoji, args.chat_id, args.message_id);
|
|
76349
76493
|
}
|
|
76350
76494
|
function deleteFromHistory(args) {
|
|
76351
|
-
|
|
76352
|
-
|
|
76353
|
-
|
|
76354
|
-
|
|
76495
|
+
prep(`
|
|
76496
|
+
DELETE FROM messages
|
|
76497
|
+
WHERE chat_id = ? AND message_id = ?
|
|
76498
|
+
`).run(args.chat_id, args.message_id);
|
|
76355
76499
|
}
|
|
76356
76500
|
function getLatestInboundMessageId(chatId, threadId) {
|
|
76357
76501
|
const params = [chatId];
|
|
@@ -76365,12 +76509,14 @@ function getLatestInboundMessageId(chatId, threadId) {
|
|
|
76365
76509
|
}
|
|
76366
76510
|
}
|
|
76367
76511
|
sql += " ORDER BY ts DESC, message_id DESC LIMIT 1";
|
|
76368
|
-
const row =
|
|
76512
|
+
const row = prep(sql).get(...params);
|
|
76369
76513
|
return row?.message_id ?? null;
|
|
76370
76514
|
}
|
|
76371
76515
|
function lookupMessageRoleAndText(chatId, messageId, opts) {
|
|
76516
|
+
if (db == null)
|
|
76517
|
+
return null;
|
|
76372
76518
|
const sql = `SELECT role, text, kind FROM messages WHERE chat_id = ? AND message_id = ?` + (opts?.includeSystem === true ? "" : ` AND role <> 'system'`) + ` LIMIT 1`;
|
|
76373
|
-
const row =
|
|
76519
|
+
const row = prep(sql).get(chatId, messageId);
|
|
76374
76520
|
if (!row)
|
|
76375
76521
|
return null;
|
|
76376
76522
|
return { role: row.role, text: row.text ?? "", kind: row.kind ?? null };
|
|
@@ -76390,7 +76536,7 @@ function hasOutboundDeliveredSince(chatId, sinceMs, threadId, minChars = 200) {
|
|
|
76390
76536
|
}
|
|
76391
76537
|
}
|
|
76392
76538
|
sql += " LIMIT 1";
|
|
76393
|
-
const row =
|
|
76539
|
+
const row = prep(sql).get(...params);
|
|
76394
76540
|
return row != null;
|
|
76395
76541
|
} catch {
|
|
76396
76542
|
return false;
|
|
@@ -76416,7 +76562,7 @@ function hasOutboundWithText(chatId, text4, threadId, sinceMs) {
|
|
|
76416
76562
|
params.push(Math.floor(sinceMs / 1000));
|
|
76417
76563
|
}
|
|
76418
76564
|
sql += " ORDER BY ts DESC LIMIT 500";
|
|
76419
|
-
const rows =
|
|
76565
|
+
const rows = prep(sql).all(...params);
|
|
76420
76566
|
for (const r of rows) {
|
|
76421
76567
|
const hay = normalizeDeliveryText(r.text ?? "");
|
|
76422
76568
|
if (hay.length === 0)
|
|
@@ -76442,6 +76588,10 @@ function deliveryTextMatch(hay, needle) {
|
|
|
76442
76588
|
return hay.startsWith(needle) || needle.startsWith(hay);
|
|
76443
76589
|
}
|
|
76444
76590
|
function query(opts) {
|
|
76591
|
+
if (db == null) {
|
|
76592
|
+
warnHistory("query: history DB is not open \u2014 returning no rows. If a reopen failed, the " + "orphaned-db-sweep is alarming about it every tick; RESTART the gateway.");
|
|
76593
|
+
return [];
|
|
76594
|
+
}
|
|
76445
76595
|
const limit = Math.min(MAX_LIMIT, Math.max(1, opts.limit ?? DEFAULT_LIMIT));
|
|
76446
76596
|
const params = [opts.chat_id];
|
|
76447
76597
|
let sql = "SELECT * FROM messages WHERE chat_id = ?";
|
|
@@ -76461,11 +76611,126 @@ function query(opts) {
|
|
|
76461
76611
|
}
|
|
76462
76612
|
sql += " ORDER BY ts DESC, message_id DESC LIMIT ?";
|
|
76463
76613
|
params.push(limit);
|
|
76464
|
-
const rows =
|
|
76614
|
+
const rows = prep(sql).all(...params);
|
|
76465
76615
|
rows.reverse();
|
|
76466
76616
|
return rows;
|
|
76467
76617
|
}
|
|
76468
76618
|
|
|
76619
|
+
// gateway/orphaned-db-sweep.ts
|
|
76620
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
76621
|
+
import { opendir, readlink } from "fs/promises";
|
|
76622
|
+
import { basename as basename9 } from "path";
|
|
76623
|
+
var DELETED_SUFFIX = " (deleted)";
|
|
76624
|
+
var DB_BASENAME_RE = /\.db(-wal|-shm|-journal)?$/;
|
|
76625
|
+
function resolveStateDirPrefix(stateDir) {
|
|
76626
|
+
let resolved;
|
|
76627
|
+
try {
|
|
76628
|
+
resolved = realpathSync2(stateDir);
|
|
76629
|
+
} catch {
|
|
76630
|
+
return null;
|
|
76631
|
+
}
|
|
76632
|
+
return resolved.endsWith("/") ? resolved : resolved + "/";
|
|
76633
|
+
}
|
|
76634
|
+
async function detectOrphanedDbFds(stateDir) {
|
|
76635
|
+
if (process.platform !== "linux")
|
|
76636
|
+
return [];
|
|
76637
|
+
const prefix = resolveStateDirPrefix(stateDir);
|
|
76638
|
+
if (prefix == null)
|
|
76639
|
+
return [];
|
|
76640
|
+
const found = [];
|
|
76641
|
+
try {
|
|
76642
|
+
const dir = await opendir("/proc/self/fd");
|
|
76643
|
+
for await (const entry of dir) {
|
|
76644
|
+
let target;
|
|
76645
|
+
try {
|
|
76646
|
+
target = await readlink(`/proc/self/fd/${entry.name}`);
|
|
76647
|
+
} catch {
|
|
76648
|
+
continue;
|
|
76649
|
+
}
|
|
76650
|
+
const deleted = target.endsWith(DELETED_SUFFIX);
|
|
76651
|
+
const bare = deleted ? target.slice(0, -DELETED_SUFFIX.length) : target;
|
|
76652
|
+
if (!bare.startsWith(prefix))
|
|
76653
|
+
continue;
|
|
76654
|
+
if (!deleted)
|
|
76655
|
+
continue;
|
|
76656
|
+
if (!DB_BASENAME_RE.test(basename9(bare)))
|
|
76657
|
+
continue;
|
|
76658
|
+
found.push({ fd: Number(entry.name), target });
|
|
76659
|
+
}
|
|
76660
|
+
} catch {
|
|
76661
|
+
return found;
|
|
76662
|
+
}
|
|
76663
|
+
return found;
|
|
76664
|
+
}
|
|
76665
|
+
function orphanBasename(target) {
|
|
76666
|
+
return basename9(target.endsWith(DELETED_SUFFIX) ? target.slice(0, -DELETED_SUFFIX.length) : target);
|
|
76667
|
+
}
|
|
76668
|
+
async function runOrphanedDbSweepTick(opts) {
|
|
76669
|
+
if (process.platform === "linux" && resolveStateDirPrefix(opts.stateDir) == null) {
|
|
76670
|
+
opts.log(`telegram gateway: orphaned-db-sweep cannot resolve stateDir=${opts.stateDir} \u2014` + ` deleted-inode DB detection is DISABLED until it exists and is readable.
|
|
76671
|
+
`);
|
|
76672
|
+
}
|
|
76673
|
+
const orphans = await detectOrphanedDbFds(opts.stateDir);
|
|
76674
|
+
let historyHandled = false;
|
|
76675
|
+
if (orphans.length > 0) {
|
|
76676
|
+
const names = orphans.map((o) => orphanBasename(o.target));
|
|
76677
|
+
opts.log(`telegram gateway: orphaned-db-sweep DETECTED ${orphans.length} deleted-inode DB handle(s): ` + orphans.map((o) => `fd=${o.fd} ${o.target}`).join(", ") + ` \u2014 another process unlinked these files while we held them open; every row written` + ` since the last checkpoint is LOST and further writes would be lost too.
|
|
76678
|
+
`);
|
|
76679
|
+
if (names.some((n) => n.startsWith("history.db"))) {
|
|
76680
|
+
historyHandled = true;
|
|
76681
|
+
if (opts.reopenHistory) {
|
|
76682
|
+
attemptHistoryReopen(opts, "reopened history.db");
|
|
76683
|
+
} else {
|
|
76684
|
+
opts.log(`telegram gateway: orphaned-db-sweep found an orphaned history.db handle but no reopen` + ` is wired (history disabled) \u2014 RESTART the gateway to recover.
|
|
76685
|
+
`);
|
|
76686
|
+
}
|
|
76687
|
+
}
|
|
76688
|
+
if (names.some((n) => n.startsWith("registry.db"))) {
|
|
76689
|
+
opts.log(`telegram gateway: orphaned-db-sweep found an orphaned registry.db handle. An in-process` + ` reopen is NOT safe here \u2014 the turnsDb handle is captured by value into long-lived` + ` wiring, so closing it would leave those consumers on a closed handle. RESTART the` + ` gateway to recover; subagent/turn rows written since the last checkpoint are LOST.
|
|
76690
|
+
`);
|
|
76691
|
+
}
|
|
76692
|
+
const unowned = [...new Set(names.filter((n) => !n.startsWith("history.db") && !n.startsWith("registry.db")))];
|
|
76693
|
+
if (unowned.length > 0) {
|
|
76694
|
+
opts.log(`telegram gateway: orphaned-db-sweep found orphaned handle(s) on ${unowned.join(", ")},` + ` which no recovery lane owns \u2014 the gateway cannot reopen them in place. RESTART the` + ` gateway to recover; rows written to those files since the last checkpoint are LOST.
|
|
76695
|
+
`);
|
|
76696
|
+
}
|
|
76697
|
+
}
|
|
76698
|
+
if (!historyHandled) {
|
|
76699
|
+
const stuck = opts.historyReopenFailure?.();
|
|
76700
|
+
if (stuck != null && stuck !== "") {
|
|
76701
|
+
opts.log(`telegram gateway: orphaned-db-sweep history.db is CLOSED and a previous reopen failed` + ` (${stuck}) \u2014 every history read and write is dead and no fd evidence remains.` + ` Retrying the reopen; RESTART the gateway if this keeps repeating.
|
|
76702
|
+
`);
|
|
76703
|
+
if (opts.reopenHistory)
|
|
76704
|
+
attemptHistoryReopen(opts, "recovered history.db");
|
|
76705
|
+
}
|
|
76706
|
+
}
|
|
76707
|
+
return orphans;
|
|
76708
|
+
}
|
|
76709
|
+
function attemptHistoryReopen(opts, successVerb) {
|
|
76710
|
+
try {
|
|
76711
|
+
opts.reopenHistory?.();
|
|
76712
|
+
opts.log(`telegram gateway: orphaned-db-sweep ${successVerb} \u2014 writes are durable again` + ` (proved by the post-reopen writer self-check); rows written since the last` + ` checkpoint are NOT recoverable.
|
|
76713
|
+
`);
|
|
76714
|
+
} catch (err) {
|
|
76715
|
+
opts.log(`telegram gateway: orphaned-db-sweep FAILED to reopen history.db: ${err.message}` + ` \u2014 history writes are NOT durable; RESTART the gateway.
|
|
76716
|
+
`);
|
|
76717
|
+
}
|
|
76718
|
+
}
|
|
76719
|
+
var DEFAULT_INTERVAL_MS = 5 * 60000;
|
|
76720
|
+
function startOrphanedDbSweep(opts) {
|
|
76721
|
+
let running = false;
|
|
76722
|
+
const timer3 = setInterval(() => {
|
|
76723
|
+
if (running)
|
|
76724
|
+
return;
|
|
76725
|
+
running = true;
|
|
76726
|
+
runOrphanedDbSweepTick(opts).catch(() => {}).finally(() => {
|
|
76727
|
+
running = false;
|
|
76728
|
+
});
|
|
76729
|
+
}, opts.intervalMs ?? DEFAULT_INTERVAL_MS);
|
|
76730
|
+
timer3.unref?.();
|
|
76731
|
+
return () => clearInterval(timer3);
|
|
76732
|
+
}
|
|
76733
|
+
|
|
76469
76734
|
// shared/sent-text-capture.ts
|
|
76470
76735
|
var SENT_TEXT2 = Symbol.for("switchroom.telegram.sentText");
|
|
76471
76736
|
function readSentText(message) {
|
|
@@ -88227,7 +88492,7 @@ async function discoverModels(agentName3, opts = {}) {
|
|
|
88227
88492
|
init_atomic();
|
|
88228
88493
|
|
|
88229
88494
|
// ../src/util/shipped-assets.ts
|
|
88230
|
-
import { existsSync as existsSync35, readFileSync as readFileSync34, realpathSync as
|
|
88495
|
+
import { existsSync as existsSync35, readFileSync as readFileSync34, realpathSync as realpathSync3 } from "node:fs";
|
|
88231
88496
|
import { dirname as dirname20, resolve as resolve7 } from "node:path";
|
|
88232
88497
|
var FHS_SHARE_ROOTS = [
|
|
88233
88498
|
"/usr/local/share/switchroom",
|
|
@@ -88276,7 +88541,7 @@ function resolveShippedAsset(spec, probe) {
|
|
|
88276
88541
|
return { path: null, candidates: candidates.map((x) => x.path), source: "none" };
|
|
88277
88542
|
}
|
|
88278
88543
|
function canonicalise(path2, probe) {
|
|
88279
|
-
const realpath = probe.realpath ??
|
|
88544
|
+
const realpath = probe.realpath ?? realpathSync3;
|
|
88280
88545
|
try {
|
|
88281
88546
|
return realpath(path2);
|
|
88282
88547
|
} catch {
|
|
@@ -88354,7 +88619,7 @@ var AUDIT_ROOT = join45(homedir11(), ".switchroom", "audit");
|
|
|
88354
88619
|
|
|
88355
88620
|
// ../src/agents/profiles.ts
|
|
88356
88621
|
var import_handlebars = __toESM(require_lib(), 1);
|
|
88357
|
-
import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync37, readdirSync as readdirSync9, statSync as statSync14, copyFileSync, mkdirSync as mkdirSync33, realpathSync as
|
|
88622
|
+
import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync37, readdirSync as readdirSync9, statSync as statSync14, copyFileSync, mkdirSync as mkdirSync33, realpathSync as realpathSync4 } from "node:fs";
|
|
88358
88623
|
import { resolve as resolve8, join as join46, sep as pathSep } from "node:path";
|
|
88359
88624
|
function resolveProfilesRootDetailed() {
|
|
88360
88625
|
return resolveShippedAsset(PROFILES_ASSET, {
|
|
@@ -97859,7 +98124,7 @@ function buildSilencePokeOptions(deps) {
|
|
|
97859
98124
|
var MAX_LABEL_CHARS = 60;
|
|
97860
98125
|
var MAX_BASH_CHARS = 40;
|
|
97861
98126
|
var MAX_DESCRIPTION_CHARS = 160;
|
|
97862
|
-
function
|
|
98127
|
+
function basename11(p) {
|
|
97863
98128
|
if (!p)
|
|
97864
98129
|
return "";
|
|
97865
98130
|
const parts = p.split("/").filter(Boolean);
|
|
@@ -97932,7 +98197,7 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
|
|
|
97932
98197
|
const pre = preambleLabel();
|
|
97933
98198
|
if (pre)
|
|
97934
98199
|
return pre;
|
|
97935
|
-
return truncate4(
|
|
98200
|
+
return truncate4(basename11(str("file_path") ?? ""));
|
|
97936
98201
|
}
|
|
97937
98202
|
case "Bash":
|
|
97938
98203
|
case "BashOutput": {
|
|
@@ -98017,7 +98282,7 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
|
|
|
98017
98282
|
const v = str(k);
|
|
98018
98283
|
if (v != null && v.length > 0) {
|
|
98019
98284
|
if (k === "file_path" || k === "path")
|
|
98020
|
-
return truncate4(
|
|
98285
|
+
return truncate4(basename11(v));
|
|
98021
98286
|
if (k === "url")
|
|
98022
98287
|
return truncate4(hostFromUrl(v));
|
|
98023
98288
|
if (k === "description")
|
|
@@ -99821,7 +100086,7 @@ function sanitiseToolArg(name, raw) {
|
|
|
99821
100086
|
case "NotebookEdit": {
|
|
99822
100087
|
const fp = raw.file_path;
|
|
99823
100088
|
if (typeof fp === "string" && fp.length > 0)
|
|
99824
|
-
out =
|
|
100089
|
+
out = basename12(fp);
|
|
99825
100090
|
break;
|
|
99826
100091
|
}
|
|
99827
100092
|
case "Bash": {
|
|
@@ -99857,7 +100122,7 @@ function sanitiseToolArg(name, raw) {
|
|
|
99857
100122
|
out = out.slice(0, SANITISE_MAX_LEN - 1) + "\u2026";
|
|
99858
100123
|
return out;
|
|
99859
100124
|
}
|
|
99860
|
-
function
|
|
100125
|
+
function basename12(p) {
|
|
99861
100126
|
const idx = p.lastIndexOf("/");
|
|
99862
100127
|
return idx === -1 ? p : p.slice(idx + 1);
|
|
99863
100128
|
}
|
|
@@ -101174,13 +101439,13 @@ function recordExists(id) {
|
|
|
101174
101439
|
}
|
|
101175
101440
|
|
|
101176
101441
|
// worktree-watch-cwds.ts
|
|
101177
|
-
import { realpathSync as
|
|
101178
|
-
import { basename as
|
|
101442
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
101443
|
+
import { basename as basename14 } from "node:path";
|
|
101179
101444
|
var identityEscalated = false;
|
|
101180
101445
|
function defaultDeriveName(agentDir) {
|
|
101181
101446
|
if (!agentDir || agentDir.trim().length === 0)
|
|
101182
101447
|
return "";
|
|
101183
|
-
const leaf =
|
|
101448
|
+
const leaf = basename14(agentDir).trim();
|
|
101184
101449
|
return leaf;
|
|
101185
101450
|
}
|
|
101186
101451
|
function resolveOwnerIdentity(self, agentDir, deriveName) {
|
|
@@ -101200,7 +101465,7 @@ function ownedWorktreeCwds(opts) {
|
|
|
101200
101465
|
}
|
|
101201
101466
|
return [];
|
|
101202
101467
|
}
|
|
101203
|
-
const rp = opts.realpath ??
|
|
101468
|
+
const rp = opts.realpath ?? realpathSync5;
|
|
101204
101469
|
try {
|
|
101205
101470
|
return opts.listRecords().filter((r) => r.ownerAgent === resolved).map((r) => {
|
|
101206
101471
|
try {
|
|
@@ -102020,7 +102285,7 @@ function defaultReadEvents(stateDir) {
|
|
|
102020
102285
|
}
|
|
102021
102286
|
// permission-title.ts
|
|
102022
102287
|
init_card_format();
|
|
102023
|
-
import { basename as
|
|
102288
|
+
import { basename as basename15 } from "node:path";
|
|
102024
102289
|
init_redact();
|
|
102025
102290
|
var COMMAND_TITLE_MAX2 = 48;
|
|
102026
102291
|
var DESCRIPTION_LINE_MAX = 240;
|
|
@@ -102274,11 +102539,11 @@ function describeGrant(toolName, inputPreview, option) {
|
|
|
102274
102539
|
return m ? `run ${m[1]} commands` : "run that command";
|
|
102275
102540
|
}
|
|
102276
102541
|
if (t === "Edit" || t === "MultiEdit" || t === "NotebookEdit")
|
|
102277
|
-
return `edit ${
|
|
102542
|
+
return `edit ${basename15(arg)}`;
|
|
102278
102543
|
if (t === "Write")
|
|
102279
|
-
return `write ${
|
|
102544
|
+
return `write ${basename15(arg)}`;
|
|
102280
102545
|
if (t === "Read")
|
|
102281
|
-
return `read ${
|
|
102546
|
+
return `read ${basename15(arg)}`;
|
|
102282
102547
|
return naturalAction2(toolName, inputPreview);
|
|
102283
102548
|
}
|
|
102284
102549
|
switch (rule) {
|
|
@@ -102317,12 +102582,12 @@ function fileBase2(input, rawPreview) {
|
|
|
102317
102582
|
if (input) {
|
|
102318
102583
|
const p = readString3(input, "file_path") ?? readString3(input, "notebook_path");
|
|
102319
102584
|
if (p)
|
|
102320
|
-
return
|
|
102585
|
+
return basename15(p);
|
|
102321
102586
|
}
|
|
102322
102587
|
if (rawPreview) {
|
|
102323
102588
|
const p = extractFilePathFromRaw3(rawPreview);
|
|
102324
102589
|
if (p)
|
|
102325
|
-
return
|
|
102590
|
+
return basename15(p);
|
|
102326
102591
|
}
|
|
102327
102592
|
return null;
|
|
102328
102593
|
}
|
|
@@ -102453,7 +102718,7 @@ function truncate7(text4, max) {
|
|
|
102453
102718
|
}
|
|
102454
102719
|
|
|
102455
102720
|
// permission-rule.ts
|
|
102456
|
-
import { basename as
|
|
102721
|
+
import { basename as basename16 } from "node:path";
|
|
102457
102722
|
var FILE_TOOLS2 = new Set([
|
|
102458
102723
|
"Edit",
|
|
102459
102724
|
"Write",
|
|
@@ -102585,14 +102850,14 @@ function skillBasenameFromPath4(input) {
|
|
|
102585
102850
|
if (!path3)
|
|
102586
102851
|
return null;
|
|
102587
102852
|
const trimmed = path3.replace(/\/SKILL\.md$/i, "").replace(/\/$/, "");
|
|
102588
|
-
return
|
|
102853
|
+
return basename16(trimmed) || null;
|
|
102589
102854
|
}
|
|
102590
102855
|
function isRulePersisted(resolvedAllow, ruleRule) {
|
|
102591
102856
|
return resolvedAllow.includes(ruleRule);
|
|
102592
102857
|
}
|
|
102593
102858
|
|
|
102594
102859
|
// scoped-approval.ts
|
|
102595
|
-
import { basename as
|
|
102860
|
+
import { basename as basename17 } from "node:path";
|
|
102596
102861
|
var SCOPED_APPROVAL_DEFAULT_TTL_MS = 30 * 60 * 1000;
|
|
102597
102862
|
function scopedApprovalTtlMs(env = process.env) {
|
|
102598
102863
|
const raw = env.SWITCHROOM_SCOPED_APPROVAL_TTL_MS;
|
|
@@ -102617,7 +102882,7 @@ function resolveTimeBox(toolName, inputPreview, choices) {
|
|
|
102617
102882
|
const fileMatch = FILE_RULE.exec(rule);
|
|
102618
102883
|
if (fileMatch) {
|
|
102619
102884
|
const verb = fileMatch[1] === "Read" ? "reads of" : "edits to";
|
|
102620
|
-
return { rule, breadth: `${verb} ${
|
|
102885
|
+
return { rule, breadth: `${verb} ${basename17(fileMatch[2])}` };
|
|
102621
102886
|
}
|
|
102622
102887
|
const bashMatch = BASH_FAMILY_RULE.exec(rule);
|
|
102623
102888
|
if (bashMatch) {
|
|
@@ -104169,10 +104434,10 @@ function startOutboxSweep(deps) {
|
|
|
104169
104434
|
}
|
|
104170
104435
|
|
|
104171
104436
|
// ../src/build-info.ts
|
|
104172
|
-
var VERSION2 = "0.21.
|
|
104173
|
-
var COMMIT_SHA = "
|
|
104174
|
-
var COMMIT_DATE = "2026-08-
|
|
104175
|
-
var LATEST_PR =
|
|
104437
|
+
var VERSION2 = "0.21.4";
|
|
104438
|
+
var COMMIT_SHA = "aa63e74c";
|
|
104439
|
+
var COMMIT_DATE = "2026-08-10T17:32:03Z";
|
|
104440
|
+
var LATEST_PR = 4597;
|
|
104176
104441
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
104177
104442
|
|
|
104178
104443
|
// gateway/boot-version.ts
|
|
@@ -106768,8 +107033,8 @@ function assertSendable(f) {
|
|
|
106768
107033
|
}
|
|
106769
107034
|
let real, stateReal;
|
|
106770
107035
|
try {
|
|
106771
|
-
real =
|
|
106772
|
-
stateReal =
|
|
107036
|
+
real = realpathSync6(f);
|
|
107037
|
+
stateReal = realpathSync6(STATE_DIR);
|
|
106773
107038
|
} catch {
|
|
106774
107039
|
throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
|
|
106775
107040
|
}
|
|
@@ -107058,6 +107323,8 @@ if (isGatewayMain)
|
|
|
107058
107323
|
if (isGatewayMain && !STATIC) {
|
|
107059
107324
|
setInterval(() => runHistoryReaperNow("periodic"), REGISTRY_REAPER_INTERVAL_MS).unref();
|
|
107060
107325
|
}
|
|
107326
|
+
if (isGatewayMain && !STATIC)
|
|
107327
|
+
startOrphanedDbSweep({ stateDir: STATE_DIR, reopenHistory: HISTORY_ENABLED ? () => reopenHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30) : undefined, historyReopenFailure: HISTORY_ENABLED ? getHistoryReopenFailure : undefined, log: (l) => process.stderr.write(l) });
|
|
107061
107328
|
function checkApprovals() {
|
|
107062
107329
|
let files;
|
|
107063
107330
|
try {
|
|
@@ -110628,7 +110895,7 @@ if (isGatewayMain)
|
|
|
110628
110895
|
if (turnsDb != null && msg.activeFile != null) {
|
|
110629
110896
|
const stampKey = currentTurn?.registryKey ?? null;
|
|
110630
110897
|
if (stampKey != null && stampKey !== lastSessionStampedTurnKey) {
|
|
110631
|
-
const sessionId =
|
|
110898
|
+
const sessionId = basename18(msg.activeFile).replace(/\.jsonl$/, "");
|
|
110632
110899
|
if (sessionId) {
|
|
110633
110900
|
try {
|
|
110634
110901
|
stampTurnSessionId(turnsDb, stampKey, sessionId);
|
|
@@ -113361,7 +113628,7 @@ function getMyAgentName() {
|
|
|
113361
113628
|
const fromEnv = process.env.SWITCHROOM_AGENT_NAME;
|
|
113362
113629
|
if (fromEnv && fromEnv.trim().length > 0)
|
|
113363
113630
|
return fromEnv.trim();
|
|
113364
|
-
return
|
|
113631
|
+
return basename18(process.cwd());
|
|
113365
113632
|
}
|
|
113366
113633
|
function isSelfTargetingCommand(name) {
|
|
113367
113634
|
if (name === "all")
|