switchroom 0.21.3 → 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/cli/switchroom.js +79 -16
- package/dist/host-control/main.js +1 -1
- package/package.json +3 -2
- package/telegram-plugin/dist/gateway/gateway.js +360 -102
- 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
|
|
@@ -39741,7 +39816,7 @@ import {
|
|
|
39741
39816
|
rmSync as rmSync9,
|
|
39742
39817
|
statSync as statSync25,
|
|
39743
39818
|
renameSync as renameSync30,
|
|
39744
|
-
realpathSync as
|
|
39819
|
+
realpathSync as realpathSync6,
|
|
39745
39820
|
chmodSync as chmodSync14,
|
|
39746
39821
|
openSync as openSync18,
|
|
39747
39822
|
closeSync as closeSync18,
|
|
@@ -39764,7 +39839,7 @@ function fsyncPathSync(path) {
|
|
|
39764
39839
|
|
|
39765
39840
|
// gateway/gateway.ts
|
|
39766
39841
|
import { homedir as homedir22 } from "os";
|
|
39767
|
-
import { join as join76, sep as sep6, basename as
|
|
39842
|
+
import { join as join76, sep as sep6, basename as basename18 } from "path";
|
|
39768
39843
|
|
|
39769
39844
|
// plugin-logger.ts
|
|
39770
39845
|
import { appendFileSync, mkdirSync, renameSync as renameSync2, statSync, existsSync } from "fs";
|
|
@@ -76091,6 +76166,34 @@ var DEFAULT_LIMIT = 10;
|
|
|
76091
76166
|
var MAX_LIMIT = 50;
|
|
76092
76167
|
var db = null;
|
|
76093
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
|
+
}
|
|
76094
76197
|
function warnHistory(msg) {
|
|
76095
76198
|
try {
|
|
76096
76199
|
process.stderr.write(`telegram history: ${msg}
|
|
@@ -76153,7 +76256,7 @@ function initHistory(stateDir, retentionDays = 30) {
|
|
|
76153
76256
|
}
|
|
76154
76257
|
}
|
|
76155
76258
|
const LOGICAL_KEY_INDEX = "idx_messages_logical_key";
|
|
76156
|
-
const logicalKeyIndexExists =
|
|
76259
|
+
const logicalKeyIndexExists = prep(`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ?`).get(LOGICAL_KEY_INDEX) != null;
|
|
76157
76260
|
if (!logicalKeyIndexExists) {
|
|
76158
76261
|
db.exec(`
|
|
76159
76262
|
DELETE FROM messages
|
|
@@ -76182,7 +76285,7 @@ function initHistory(stateDir, retentionDays = 30) {
|
|
|
76182
76285
|
adoptSqliteOwnership(path2);
|
|
76183
76286
|
if (retentionDays > 0) {
|
|
76184
76287
|
const cutoff = Math.floor(Date.now() / 1000) - retentionDays * 86400;
|
|
76185
|
-
|
|
76288
|
+
prep("DELETE FROM messages WHERE ts < ?").run(cutoff);
|
|
76186
76289
|
}
|
|
76187
76290
|
const check = verifyHistoryWritable();
|
|
76188
76291
|
if (!check.ok) {
|
|
@@ -76195,11 +76298,11 @@ function verifyHistoryWritable() {
|
|
|
76195
76298
|
const SENTINEL_CHAT = "__history_selfcheck__";
|
|
76196
76299
|
const sentinelId = Date.now();
|
|
76197
76300
|
try {
|
|
76198
|
-
|
|
76199
|
-
|
|
76301
|
+
prep("DELETE FROM messages WHERE chat_id = ?").run(SENTINEL_CHAT);
|
|
76302
|
+
prep(`INSERT OR REPLACE INTO messages
|
|
76200
76303
|
(chat_id, thread_id, message_id, role, ts, text)
|
|
76201
76304
|
VALUES (?, NULL, ?, 'assistant', ?, ?)`).run(SENTINEL_CHAT, sentinelId, Math.floor(Date.now() / 1000), "selfcheck");
|
|
76202
|
-
const row =
|
|
76305
|
+
const row = prep("SELECT text FROM messages WHERE chat_id = ? AND message_id = ?").get(SENTINEL_CHAT, sentinelId);
|
|
76203
76306
|
if (row?.text !== "selfcheck") {
|
|
76204
76307
|
return { ok: false, error: "sentinel row not read back after insert" };
|
|
76205
76308
|
}
|
|
@@ -76208,7 +76311,7 @@ function verifyHistoryWritable() {
|
|
|
76208
76311
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
76209
76312
|
} finally {
|
|
76210
76313
|
try {
|
|
76211
|
-
|
|
76314
|
+
prep("DELETE FROM messages WHERE chat_id = ?").run(SENTINEL_CHAT);
|
|
76212
76315
|
} catch {}
|
|
76213
76316
|
}
|
|
76214
76317
|
}
|
|
@@ -76216,7 +76319,7 @@ function checkpointWal() {
|
|
|
76216
76319
|
if (db == null)
|
|
76217
76320
|
return false;
|
|
76218
76321
|
try {
|
|
76219
|
-
|
|
76322
|
+
prep("PRAGMA wal_checkpoint(TRUNCATE)").run();
|
|
76220
76323
|
if (dbPath) {
|
|
76221
76324
|
for (const suffix of ["-shm", "-wal"]) {
|
|
76222
76325
|
const f = dbPath + suffix;
|
|
@@ -76233,13 +76336,45 @@ function checkpointWal() {
|
|
|
76233
76336
|
return false;
|
|
76234
76337
|
}
|
|
76235
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
|
+
}
|
|
76236
76371
|
function pruneMessagesOlderThanDays(retentionDays, nowSec, batchLimit = 5000) {
|
|
76237
76372
|
if (db == null)
|
|
76238
76373
|
return 0;
|
|
76239
76374
|
if (retentionDays <= 0)
|
|
76240
76375
|
return 0;
|
|
76241
76376
|
const cutoffSec = (nowSec ?? Math.floor(Date.now() / 1000)) - retentionDays * 86400;
|
|
76242
|
-
const stmt =
|
|
76377
|
+
const stmt = prep(`
|
|
76243
76378
|
DELETE FROM messages
|
|
76244
76379
|
WHERE rowid IN (
|
|
76245
76380
|
SELECT rowid FROM messages WHERE ts < ? LIMIT ?
|
|
@@ -76268,7 +76403,7 @@ function recordInbound(args) {
|
|
|
76268
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`);
|
|
76269
76404
|
return;
|
|
76270
76405
|
}
|
|
76271
|
-
const stmt =
|
|
76406
|
+
const stmt = prep(`
|
|
76272
76407
|
INSERT OR REPLACE INTO messages
|
|
76273
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)
|
|
76274
76409
|
VALUES (?, ?, ?, 'user', ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -76295,12 +76430,12 @@ function recordOutbound(args) {
|
|
|
76295
76430
|
if (validRows.length === 0)
|
|
76296
76431
|
return;
|
|
76297
76432
|
const groupId = validRows[0].id;
|
|
76298
|
-
const stmt =
|
|
76433
|
+
const stmt = prep(`
|
|
76299
76434
|
INSERT OR REPLACE INTO messages
|
|
76300
76435
|
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id)
|
|
76301
76436
|
VALUES (?, ?, ?, 'assistant', NULL, NULL, ?, ?, ?, ?)
|
|
76302
76437
|
`);
|
|
76303
|
-
const dropSystem =
|
|
76438
|
+
const dropSystem = prep(`DELETE FROM messages WHERE chat_id = ? AND message_id = ? AND role = 'system'`);
|
|
76304
76439
|
const tx = requireDb().transaction((rows) => {
|
|
76305
76440
|
for (const r of rows) {
|
|
76306
76441
|
dropSystem.run(args.chat_id, r.id);
|
|
@@ -76320,8 +76455,8 @@ function recordSystemOutbound(args) {
|
|
|
76320
76455
|
if (db == null)
|
|
76321
76456
|
return false;
|
|
76322
76457
|
try {
|
|
76323
|
-
const res =
|
|
76324
|
-
|
|
76458
|
+
const res = prep(`
|
|
76459
|
+
INSERT INTO messages
|
|
76325
76460
|
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id, kind)
|
|
76326
76461
|
SELECT ?, ?, ?, 'system', NULL, NULL, ?, ?, NULL, NULL, ?
|
|
76327
76462
|
WHERE NOT EXISTS (
|
|
@@ -76336,31 +76471,31 @@ function recordSystemOutbound(args) {
|
|
|
76336
76471
|
}
|
|
76337
76472
|
function updateSystemOutboundText(args) {
|
|
76338
76473
|
try {
|
|
76339
|
-
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);
|
|
76340
76475
|
return (res?.changes ?? 0) > 0;
|
|
76341
76476
|
} catch {
|
|
76342
76477
|
return false;
|
|
76343
76478
|
}
|
|
76344
76479
|
}
|
|
76345
76480
|
function recordEdit(args) {
|
|
76346
|
-
|
|
76347
|
-
|
|
76348
|
-
|
|
76349
|
-
|
|
76350
|
-
|
|
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);
|
|
76351
76486
|
}
|
|
76352
76487
|
function recordReaction(args) {
|
|
76353
|
-
|
|
76354
|
-
|
|
76355
|
-
|
|
76356
|
-
|
|
76357
|
-
|
|
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);
|
|
76358
76493
|
}
|
|
76359
76494
|
function deleteFromHistory(args) {
|
|
76360
|
-
|
|
76361
|
-
|
|
76362
|
-
|
|
76363
|
-
|
|
76495
|
+
prep(`
|
|
76496
|
+
DELETE FROM messages
|
|
76497
|
+
WHERE chat_id = ? AND message_id = ?
|
|
76498
|
+
`).run(args.chat_id, args.message_id);
|
|
76364
76499
|
}
|
|
76365
76500
|
function getLatestInboundMessageId(chatId, threadId) {
|
|
76366
76501
|
const params = [chatId];
|
|
@@ -76374,12 +76509,14 @@ function getLatestInboundMessageId(chatId, threadId) {
|
|
|
76374
76509
|
}
|
|
76375
76510
|
}
|
|
76376
76511
|
sql += " ORDER BY ts DESC, message_id DESC LIMIT 1";
|
|
76377
|
-
const row =
|
|
76512
|
+
const row = prep(sql).get(...params);
|
|
76378
76513
|
return row?.message_id ?? null;
|
|
76379
76514
|
}
|
|
76380
76515
|
function lookupMessageRoleAndText(chatId, messageId, opts) {
|
|
76516
|
+
if (db == null)
|
|
76517
|
+
return null;
|
|
76381
76518
|
const sql = `SELECT role, text, kind FROM messages WHERE chat_id = ? AND message_id = ?` + (opts?.includeSystem === true ? "" : ` AND role <> 'system'`) + ` LIMIT 1`;
|
|
76382
|
-
const row =
|
|
76519
|
+
const row = prep(sql).get(chatId, messageId);
|
|
76383
76520
|
if (!row)
|
|
76384
76521
|
return null;
|
|
76385
76522
|
return { role: row.role, text: row.text ?? "", kind: row.kind ?? null };
|
|
@@ -76399,7 +76536,7 @@ function hasOutboundDeliveredSince(chatId, sinceMs, threadId, minChars = 200) {
|
|
|
76399
76536
|
}
|
|
76400
76537
|
}
|
|
76401
76538
|
sql += " LIMIT 1";
|
|
76402
|
-
const row =
|
|
76539
|
+
const row = prep(sql).get(...params);
|
|
76403
76540
|
return row != null;
|
|
76404
76541
|
} catch {
|
|
76405
76542
|
return false;
|
|
@@ -76425,7 +76562,7 @@ function hasOutboundWithText(chatId, text4, threadId, sinceMs) {
|
|
|
76425
76562
|
params.push(Math.floor(sinceMs / 1000));
|
|
76426
76563
|
}
|
|
76427
76564
|
sql += " ORDER BY ts DESC LIMIT 500";
|
|
76428
|
-
const rows =
|
|
76565
|
+
const rows = prep(sql).all(...params);
|
|
76429
76566
|
for (const r of rows) {
|
|
76430
76567
|
const hay = normalizeDeliveryText(r.text ?? "");
|
|
76431
76568
|
if (hay.length === 0)
|
|
@@ -76451,6 +76588,10 @@ function deliveryTextMatch(hay, needle) {
|
|
|
76451
76588
|
return hay.startsWith(needle) || needle.startsWith(hay);
|
|
76452
76589
|
}
|
|
76453
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
|
+
}
|
|
76454
76595
|
const limit = Math.min(MAX_LIMIT, Math.max(1, opts.limit ?? DEFAULT_LIMIT));
|
|
76455
76596
|
const params = [opts.chat_id];
|
|
76456
76597
|
let sql = "SELECT * FROM messages WHERE chat_id = ?";
|
|
@@ -76470,11 +76611,126 @@ function query(opts) {
|
|
|
76470
76611
|
}
|
|
76471
76612
|
sql += " ORDER BY ts DESC, message_id DESC LIMIT ?";
|
|
76472
76613
|
params.push(limit);
|
|
76473
|
-
const rows =
|
|
76614
|
+
const rows = prep(sql).all(...params);
|
|
76474
76615
|
rows.reverse();
|
|
76475
76616
|
return rows;
|
|
76476
76617
|
}
|
|
76477
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
|
+
|
|
76478
76734
|
// shared/sent-text-capture.ts
|
|
76479
76735
|
var SENT_TEXT2 = Symbol.for("switchroom.telegram.sentText");
|
|
76480
76736
|
function readSentText(message) {
|
|
@@ -88236,7 +88492,7 @@ async function discoverModels(agentName3, opts = {}) {
|
|
|
88236
88492
|
init_atomic();
|
|
88237
88493
|
|
|
88238
88494
|
// ../src/util/shipped-assets.ts
|
|
88239
|
-
import { existsSync as existsSync35, readFileSync as readFileSync34, realpathSync as
|
|
88495
|
+
import { existsSync as existsSync35, readFileSync as readFileSync34, realpathSync as realpathSync3 } from "node:fs";
|
|
88240
88496
|
import { dirname as dirname20, resolve as resolve7 } from "node:path";
|
|
88241
88497
|
var FHS_SHARE_ROOTS = [
|
|
88242
88498
|
"/usr/local/share/switchroom",
|
|
@@ -88285,7 +88541,7 @@ function resolveShippedAsset(spec, probe) {
|
|
|
88285
88541
|
return { path: null, candidates: candidates.map((x) => x.path), source: "none" };
|
|
88286
88542
|
}
|
|
88287
88543
|
function canonicalise(path2, probe) {
|
|
88288
|
-
const realpath = probe.realpath ??
|
|
88544
|
+
const realpath = probe.realpath ?? realpathSync3;
|
|
88289
88545
|
try {
|
|
88290
88546
|
return realpath(path2);
|
|
88291
88547
|
} catch {
|
|
@@ -88363,7 +88619,7 @@ var AUDIT_ROOT = join45(homedir11(), ".switchroom", "audit");
|
|
|
88363
88619
|
|
|
88364
88620
|
// ../src/agents/profiles.ts
|
|
88365
88621
|
var import_handlebars = __toESM(require_lib(), 1);
|
|
88366
|
-
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";
|
|
88367
88623
|
import { resolve as resolve8, join as join46, sep as pathSep } from "node:path";
|
|
88368
88624
|
function resolveProfilesRootDetailed() {
|
|
88369
88625
|
return resolveShippedAsset(PROFILES_ASSET, {
|
|
@@ -97868,7 +98124,7 @@ function buildSilencePokeOptions(deps) {
|
|
|
97868
98124
|
var MAX_LABEL_CHARS = 60;
|
|
97869
98125
|
var MAX_BASH_CHARS = 40;
|
|
97870
98126
|
var MAX_DESCRIPTION_CHARS = 160;
|
|
97871
|
-
function
|
|
98127
|
+
function basename11(p) {
|
|
97872
98128
|
if (!p)
|
|
97873
98129
|
return "";
|
|
97874
98130
|
const parts = p.split("/").filter(Boolean);
|
|
@@ -97941,7 +98197,7 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
|
|
|
97941
98197
|
const pre = preambleLabel();
|
|
97942
98198
|
if (pre)
|
|
97943
98199
|
return pre;
|
|
97944
|
-
return truncate4(
|
|
98200
|
+
return truncate4(basename11(str("file_path") ?? ""));
|
|
97945
98201
|
}
|
|
97946
98202
|
case "Bash":
|
|
97947
98203
|
case "BashOutput": {
|
|
@@ -98026,7 +98282,7 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
|
|
|
98026
98282
|
const v = str(k);
|
|
98027
98283
|
if (v != null && v.length > 0) {
|
|
98028
98284
|
if (k === "file_path" || k === "path")
|
|
98029
|
-
return truncate4(
|
|
98285
|
+
return truncate4(basename11(v));
|
|
98030
98286
|
if (k === "url")
|
|
98031
98287
|
return truncate4(hostFromUrl(v));
|
|
98032
98288
|
if (k === "description")
|
|
@@ -99830,7 +100086,7 @@ function sanitiseToolArg(name, raw) {
|
|
|
99830
100086
|
case "NotebookEdit": {
|
|
99831
100087
|
const fp = raw.file_path;
|
|
99832
100088
|
if (typeof fp === "string" && fp.length > 0)
|
|
99833
|
-
out =
|
|
100089
|
+
out = basename12(fp);
|
|
99834
100090
|
break;
|
|
99835
100091
|
}
|
|
99836
100092
|
case "Bash": {
|
|
@@ -99866,7 +100122,7 @@ function sanitiseToolArg(name, raw) {
|
|
|
99866
100122
|
out = out.slice(0, SANITISE_MAX_LEN - 1) + "\u2026";
|
|
99867
100123
|
return out;
|
|
99868
100124
|
}
|
|
99869
|
-
function
|
|
100125
|
+
function basename12(p) {
|
|
99870
100126
|
const idx = p.lastIndexOf("/");
|
|
99871
100127
|
return idx === -1 ? p : p.slice(idx + 1);
|
|
99872
100128
|
}
|
|
@@ -101183,13 +101439,13 @@ function recordExists(id) {
|
|
|
101183
101439
|
}
|
|
101184
101440
|
|
|
101185
101441
|
// worktree-watch-cwds.ts
|
|
101186
|
-
import { realpathSync as
|
|
101187
|
-
import { basename as
|
|
101442
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
101443
|
+
import { basename as basename14 } from "node:path";
|
|
101188
101444
|
var identityEscalated = false;
|
|
101189
101445
|
function defaultDeriveName(agentDir) {
|
|
101190
101446
|
if (!agentDir || agentDir.trim().length === 0)
|
|
101191
101447
|
return "";
|
|
101192
|
-
const leaf =
|
|
101448
|
+
const leaf = basename14(agentDir).trim();
|
|
101193
101449
|
return leaf;
|
|
101194
101450
|
}
|
|
101195
101451
|
function resolveOwnerIdentity(self, agentDir, deriveName) {
|
|
@@ -101209,7 +101465,7 @@ function ownedWorktreeCwds(opts) {
|
|
|
101209
101465
|
}
|
|
101210
101466
|
return [];
|
|
101211
101467
|
}
|
|
101212
|
-
const rp = opts.realpath ??
|
|
101468
|
+
const rp = opts.realpath ?? realpathSync5;
|
|
101213
101469
|
try {
|
|
101214
101470
|
return opts.listRecords().filter((r) => r.ownerAgent === resolved).map((r) => {
|
|
101215
101471
|
try {
|
|
@@ -102029,7 +102285,7 @@ function defaultReadEvents(stateDir) {
|
|
|
102029
102285
|
}
|
|
102030
102286
|
// permission-title.ts
|
|
102031
102287
|
init_card_format();
|
|
102032
|
-
import { basename as
|
|
102288
|
+
import { basename as basename15 } from "node:path";
|
|
102033
102289
|
init_redact();
|
|
102034
102290
|
var COMMAND_TITLE_MAX2 = 48;
|
|
102035
102291
|
var DESCRIPTION_LINE_MAX = 240;
|
|
@@ -102283,11 +102539,11 @@ function describeGrant(toolName, inputPreview, option) {
|
|
|
102283
102539
|
return m ? `run ${m[1]} commands` : "run that command";
|
|
102284
102540
|
}
|
|
102285
102541
|
if (t === "Edit" || t === "MultiEdit" || t === "NotebookEdit")
|
|
102286
|
-
return `edit ${
|
|
102542
|
+
return `edit ${basename15(arg)}`;
|
|
102287
102543
|
if (t === "Write")
|
|
102288
|
-
return `write ${
|
|
102544
|
+
return `write ${basename15(arg)}`;
|
|
102289
102545
|
if (t === "Read")
|
|
102290
|
-
return `read ${
|
|
102546
|
+
return `read ${basename15(arg)}`;
|
|
102291
102547
|
return naturalAction2(toolName, inputPreview);
|
|
102292
102548
|
}
|
|
102293
102549
|
switch (rule) {
|
|
@@ -102326,12 +102582,12 @@ function fileBase2(input, rawPreview) {
|
|
|
102326
102582
|
if (input) {
|
|
102327
102583
|
const p = readString3(input, "file_path") ?? readString3(input, "notebook_path");
|
|
102328
102584
|
if (p)
|
|
102329
|
-
return
|
|
102585
|
+
return basename15(p);
|
|
102330
102586
|
}
|
|
102331
102587
|
if (rawPreview) {
|
|
102332
102588
|
const p = extractFilePathFromRaw3(rawPreview);
|
|
102333
102589
|
if (p)
|
|
102334
|
-
return
|
|
102590
|
+
return basename15(p);
|
|
102335
102591
|
}
|
|
102336
102592
|
return null;
|
|
102337
102593
|
}
|
|
@@ -102462,7 +102718,7 @@ function truncate7(text4, max) {
|
|
|
102462
102718
|
}
|
|
102463
102719
|
|
|
102464
102720
|
// permission-rule.ts
|
|
102465
|
-
import { basename as
|
|
102721
|
+
import { basename as basename16 } from "node:path";
|
|
102466
102722
|
var FILE_TOOLS2 = new Set([
|
|
102467
102723
|
"Edit",
|
|
102468
102724
|
"Write",
|
|
@@ -102594,14 +102850,14 @@ function skillBasenameFromPath4(input) {
|
|
|
102594
102850
|
if (!path3)
|
|
102595
102851
|
return null;
|
|
102596
102852
|
const trimmed = path3.replace(/\/SKILL\.md$/i, "").replace(/\/$/, "");
|
|
102597
|
-
return
|
|
102853
|
+
return basename16(trimmed) || null;
|
|
102598
102854
|
}
|
|
102599
102855
|
function isRulePersisted(resolvedAllow, ruleRule) {
|
|
102600
102856
|
return resolvedAllow.includes(ruleRule);
|
|
102601
102857
|
}
|
|
102602
102858
|
|
|
102603
102859
|
// scoped-approval.ts
|
|
102604
|
-
import { basename as
|
|
102860
|
+
import { basename as basename17 } from "node:path";
|
|
102605
102861
|
var SCOPED_APPROVAL_DEFAULT_TTL_MS = 30 * 60 * 1000;
|
|
102606
102862
|
function scopedApprovalTtlMs(env = process.env) {
|
|
102607
102863
|
const raw = env.SWITCHROOM_SCOPED_APPROVAL_TTL_MS;
|
|
@@ -102626,7 +102882,7 @@ function resolveTimeBox(toolName, inputPreview, choices) {
|
|
|
102626
102882
|
const fileMatch = FILE_RULE.exec(rule);
|
|
102627
102883
|
if (fileMatch) {
|
|
102628
102884
|
const verb = fileMatch[1] === "Read" ? "reads of" : "edits to";
|
|
102629
|
-
return { rule, breadth: `${verb} ${
|
|
102885
|
+
return { rule, breadth: `${verb} ${basename17(fileMatch[2])}` };
|
|
102630
102886
|
}
|
|
102631
102887
|
const bashMatch = BASH_FAMILY_RULE.exec(rule);
|
|
102632
102888
|
if (bashMatch) {
|
|
@@ -104178,10 +104434,10 @@ function startOutboxSweep(deps) {
|
|
|
104178
104434
|
}
|
|
104179
104435
|
|
|
104180
104436
|
// ../src/build-info.ts
|
|
104181
|
-
var VERSION2 = "0.21.
|
|
104182
|
-
var COMMIT_SHA = "
|
|
104183
|
-
var COMMIT_DATE = "2026-08-
|
|
104184
|
-
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;
|
|
104185
104441
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
104186
104442
|
|
|
104187
104443
|
// gateway/boot-version.ts
|
|
@@ -106777,8 +107033,8 @@ function assertSendable(f) {
|
|
|
106777
107033
|
}
|
|
106778
107034
|
let real, stateReal;
|
|
106779
107035
|
try {
|
|
106780
|
-
real =
|
|
106781
|
-
stateReal =
|
|
107036
|
+
real = realpathSync6(f);
|
|
107037
|
+
stateReal = realpathSync6(STATE_DIR);
|
|
106782
107038
|
} catch {
|
|
106783
107039
|
throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
|
|
106784
107040
|
}
|
|
@@ -107067,6 +107323,8 @@ if (isGatewayMain)
|
|
|
107067
107323
|
if (isGatewayMain && !STATIC) {
|
|
107068
107324
|
setInterval(() => runHistoryReaperNow("periodic"), REGISTRY_REAPER_INTERVAL_MS).unref();
|
|
107069
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) });
|
|
107070
107328
|
function checkApprovals() {
|
|
107071
107329
|
let files;
|
|
107072
107330
|
try {
|
|
@@ -110637,7 +110895,7 @@ if (isGatewayMain)
|
|
|
110637
110895
|
if (turnsDb != null && msg.activeFile != null) {
|
|
110638
110896
|
const stampKey = currentTurn?.registryKey ?? null;
|
|
110639
110897
|
if (stampKey != null && stampKey !== lastSessionStampedTurnKey) {
|
|
110640
|
-
const sessionId =
|
|
110898
|
+
const sessionId = basename18(msg.activeFile).replace(/\.jsonl$/, "");
|
|
110641
110899
|
if (sessionId) {
|
|
110642
110900
|
try {
|
|
110643
110901
|
stampTurnSessionId(turnsDb, stampKey, sessionId);
|
|
@@ -113370,7 +113628,7 @@ function getMyAgentName() {
|
|
|
113370
113628
|
const fromEnv = process.env.SWITCHROOM_AGENT_NAME;
|
|
113371
113629
|
if (fromEnv && fromEnv.trim().length > 0)
|
|
113372
113630
|
return fromEnv.trim();
|
|
113373
|
-
return
|
|
113631
|
+
return basename18(process.cwd());
|
|
113374
113632
|
}
|
|
113375
113633
|
function isSelfTargetingCommand(name) {
|
|
113376
113634
|
if (name === "all")
|