dsh-sessions-manager 3.6.1 → 3.6.2
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/README.en.md +3 -3
- package/README.md +3 -3
- package/lib/client.js +100 -28
- package/lib/client.js.map +2 -2
- package/lib/index.js +217 -85
- package/lib/index.js.map +2 -2
- package/package.json +1 -1
- package/src/client/index.jsx +104 -28
- package/src/client/logic.js +29 -0
- package/src/index.js +264 -86
- package/src/title-persist-index.js +13 -3
package/lib/index.js
CHANGED
|
@@ -702,7 +702,7 @@ function createSessionMetaCache(opts = {}) {
|
|
|
702
702
|
// src/title-persist-index.js
|
|
703
703
|
import { mkdir as mkdir4, readFile, rename as rename4, writeFile as writeFile4 } from "node:fs/promises";
|
|
704
704
|
import { dirname, join as join5 } from "node:path";
|
|
705
|
-
var TITLE_INDEX_SCHEMA_VERSION =
|
|
705
|
+
var TITLE_INDEX_SCHEMA_VERSION = 2;
|
|
706
706
|
var MAX_ENTRIES = 2e4;
|
|
707
707
|
function normalizeEntry(raw) {
|
|
708
708
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -711,9 +711,13 @@ function normalizeEntry(raw) {
|
|
|
711
711
|
const createdAt = typeof raw.createdAt === "number" ? raw.createdAt : null;
|
|
712
712
|
const fingerprint = typeof raw.fingerprint === "string" && raw.fingerprint ? raw.fingerprint : null;
|
|
713
713
|
const updatedAt = typeof raw.updatedAt === "number" ? raw.updatedAt : 0;
|
|
714
|
+
const resolved = raw.resolved === true || raw.resolved === 1;
|
|
714
715
|
if (!fingerprint || fingerprint.startsWith("rev:")) return null;
|
|
715
716
|
if (!title && !cwd) return null;
|
|
716
|
-
|
|
717
|
+
if (!title && !resolved) return null;
|
|
718
|
+
const entry = { title, cwd, createdAt, fingerprint, updatedAt };
|
|
719
|
+
if (resolved) entry.resolved = 1;
|
|
720
|
+
return entry;
|
|
717
721
|
}
|
|
718
722
|
function normalizeTitleIndex(raw) {
|
|
719
723
|
const entries = {};
|
|
@@ -1414,7 +1418,7 @@ function classifyLineage(header, sizeBytes) {
|
|
|
1414
1418
|
}
|
|
1415
1419
|
|
|
1416
1420
|
// src/index.js
|
|
1417
|
-
var BUILD_STAMP = true ? "3.6.
|
|
1421
|
+
var BUILD_STAMP = true ? "3.6.2+e36dc3fc" : "dev";
|
|
1418
1422
|
var name = "dsh-sessions-manager";
|
|
1419
1423
|
var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
|
|
1420
1424
|
var MAX_TITLE = 80;
|
|
@@ -1499,20 +1503,6 @@ function getActiveSessionId(context) {
|
|
|
1499
1503
|
}
|
|
1500
1504
|
return null;
|
|
1501
1505
|
}
|
|
1502
|
-
function foldTitle(events) {
|
|
1503
|
-
let found = null;
|
|
1504
|
-
let firstUser = null;
|
|
1505
|
-
for (const ev of events) {
|
|
1506
|
-
if (ev.type === "session/title" && ev.data && typeof ev.data.title === "string" && ev.data.title.length) {
|
|
1507
|
-
found = ev.data.title;
|
|
1508
|
-
}
|
|
1509
|
-
if (firstUser === null && ev.type === "user/message" && ev.data && Array.isArray(ev.data.content)) {
|
|
1510
|
-
const txt = ev.data.content.filter((b) => b && b.type === "text").map((b) => b.text).filter(Boolean).join(" ").trim();
|
|
1511
|
-
if (txt) firstUser = txt;
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
return found || firstUser || null;
|
|
1515
|
-
}
|
|
1516
1506
|
function apply(ctx) {
|
|
1517
1507
|
const w = ctx.workspaceRegistry;
|
|
1518
1508
|
const sp = ctx.sessionPersistence;
|
|
@@ -1538,7 +1528,7 @@ function apply(ctx) {
|
|
|
1538
1528
|
if (!stat5 || !entry) continue;
|
|
1539
1529
|
const fp = persistFingerprintOf(stat5);
|
|
1540
1530
|
if (!fp) continue;
|
|
1541
|
-
if (fp && entry.
|
|
1531
|
+
if (entry.fingerprint === fp && (entry.title || entry.resolved)) {
|
|
1542
1532
|
hits.set(id, { title: entry.title, cwd: entry.cwd, createdAt: entry.createdAt });
|
|
1543
1533
|
}
|
|
1544
1534
|
}
|
|
@@ -1548,10 +1538,12 @@ function apply(ctx) {
|
|
|
1548
1538
|
if (!decoded || !decoded.size) return;
|
|
1549
1539
|
const batch = {};
|
|
1550
1540
|
const now = Date.now();
|
|
1551
|
-
for (const [id,
|
|
1541
|
+
for (const [id, pack] of decoded) {
|
|
1542
|
+
if (!pack || !pack.resolved) continue;
|
|
1543
|
+
const meta = pack.meta;
|
|
1552
1544
|
const fp = persistFingerprintOf(statsById.get(id));
|
|
1553
1545
|
if (!fp) continue;
|
|
1554
|
-
batch[id] = { title: meta.title, cwd: meta.cwd, createdAt: meta.createdAt, fingerprint: fp, updatedAt: now };
|
|
1546
|
+
batch[id] = { title: meta.title, cwd: meta.cwd, createdAt: meta.createdAt, fingerprint: fp, updatedAt: now, resolved: 1 };
|
|
1555
1547
|
}
|
|
1556
1548
|
if (!Object.keys(batch).length) return;
|
|
1557
1549
|
try {
|
|
@@ -1634,6 +1626,60 @@ function apply(ctx) {
|
|
|
1634
1626
|
const warmQueue = /* @__PURE__ */ new Map();
|
|
1635
1627
|
let warmRunning = false;
|
|
1636
1628
|
let warmKickTimer = null;
|
|
1629
|
+
const WARM_MAX_ATTEMPTS = 5;
|
|
1630
|
+
const warmFail = /* @__PURE__ */ new Map();
|
|
1631
|
+
function warmFpOf(stat5) {
|
|
1632
|
+
return fingerprintOf(stat5) || persistFingerprintOf(stat5);
|
|
1633
|
+
}
|
|
1634
|
+
function warmBlocked(id, fp) {
|
|
1635
|
+
const rec = warmFail.get(id);
|
|
1636
|
+
if (!rec) return 0;
|
|
1637
|
+
if (fp && rec.fp && rec.fp !== fp) {
|
|
1638
|
+
warmFail.delete(id);
|
|
1639
|
+
return 0;
|
|
1640
|
+
}
|
|
1641
|
+
const now = Date.now();
|
|
1642
|
+
return rec.nextAt > now ? rec.nextAt - now : 0;
|
|
1643
|
+
}
|
|
1644
|
+
function warmMarkFailure(id, stat5, header) {
|
|
1645
|
+
const rec = warmFail.get(id) || { count: 0, fp: null, nextAt: 0 };
|
|
1646
|
+
rec.count += 1;
|
|
1647
|
+
rec.fp = warmFpOf(stat5) || rec.fp;
|
|
1648
|
+
if (stat5) rec.stat = stat5;
|
|
1649
|
+
if (header) rec.header = header;
|
|
1650
|
+
const backoff = rec.count >= WARM_MAX_ATTEMPTS ? 60 * 60 * 1e3 : Math.min(5 * 60 * 1e3, 15 * 1e3 * Math.pow(2, rec.count));
|
|
1651
|
+
rec.nextAt = Date.now() + backoff;
|
|
1652
|
+
warmFail.set(id, rec);
|
|
1653
|
+
}
|
|
1654
|
+
function warmPendingNow() {
|
|
1655
|
+
if (!warmHasApi) return false;
|
|
1656
|
+
if (warmRunning || warmQueue.size) return true;
|
|
1657
|
+
for (const rec of warmFail.values()) if (rec.nextAt > Date.now()) return true;
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
let warmRetryTimer = null;
|
|
1661
|
+
function scheduleWarmRetry() {
|
|
1662
|
+
if (warmRetryTimer) return;
|
|
1663
|
+
let soonest = 0;
|
|
1664
|
+
for (const rec of warmFail.values()) {
|
|
1665
|
+
const wait = rec.nextAt - Date.now();
|
|
1666
|
+
if (wait <= 0) {
|
|
1667
|
+
soonest = 1;
|
|
1668
|
+
break;
|
|
1669
|
+
}
|
|
1670
|
+
if (!soonest || wait < soonest) soonest = wait;
|
|
1671
|
+
}
|
|
1672
|
+
if (!soonest) return;
|
|
1673
|
+
warmRetryTimer = setTimeout(() => {
|
|
1674
|
+
warmRetryTimer = null;
|
|
1675
|
+
const now = Date.now();
|
|
1676
|
+
for (const [id, rec] of warmFail) {
|
|
1677
|
+
if (rec.nextAt <= now && !warmQueue.has(id)) warmQueue.set(id, { stat: rec.stat || null, header: rec.header || null });
|
|
1678
|
+
}
|
|
1679
|
+
if (warmQueue.size) scheduleWarm();
|
|
1680
|
+
}, soonest);
|
|
1681
|
+
if (typeof warmRetryTimer.unref === "function") warmRetryTimer.unref();
|
|
1682
|
+
}
|
|
1637
1683
|
function scheduleWarm() {
|
|
1638
1684
|
if (warmKickTimer || warmRunning) return;
|
|
1639
1685
|
warmKickTimer = setTimeout(() => {
|
|
@@ -1644,15 +1690,44 @@ function apply(ctx) {
|
|
|
1644
1690
|
}
|
|
1645
1691
|
function enqueueWarm(ids, statsById, entryById) {
|
|
1646
1692
|
if (!ids || !ids.length) return;
|
|
1693
|
+
if (!warmHasApi) return;
|
|
1647
1694
|
for (const id of ids) {
|
|
1648
1695
|
const key = String(id);
|
|
1696
|
+
const stat5 = statsById ? statsById.get(key) || null : null;
|
|
1697
|
+
const fp = warmFpOf(stat5);
|
|
1698
|
+
if (!fp) continue;
|
|
1699
|
+
const blockedMs = warmBlocked(key, fp);
|
|
1649
1700
|
const entry = entryById ? entryById.get(key) : null;
|
|
1701
|
+
const rec = warmFail.get(key);
|
|
1702
|
+
if (rec) {
|
|
1703
|
+
rec.stat = stat5 || rec.stat;
|
|
1704
|
+
if (entry) rec.header = entry.header || rec.header;
|
|
1705
|
+
}
|
|
1706
|
+
if (blockedMs > 0) continue;
|
|
1650
1707
|
warmQueue.set(key, {
|
|
1651
|
-
stat:
|
|
1708
|
+
stat: stat5,
|
|
1652
1709
|
header: entry && entry.header || null
|
|
1653
1710
|
});
|
|
1654
1711
|
}
|
|
1655
|
-
scheduleWarm();
|
|
1712
|
+
if (warmQueue.size) scheduleWarm();
|
|
1713
|
+
scheduleWarmRetry();
|
|
1714
|
+
}
|
|
1715
|
+
async function backfillTrashTitles(decoded) {
|
|
1716
|
+
if (!decoded || !decoded.size) return;
|
|
1717
|
+
try {
|
|
1718
|
+
const items = await readTrash();
|
|
1719
|
+
const worth = items.some((t) => !t.title && decoded.get(String(t.sessionId)) && decoded.get(String(t.sessionId)).meta && decoded.get(String(t.sessionId)).meta.title);
|
|
1720
|
+
if (!worth) return;
|
|
1721
|
+
await mutateTrash((store) => {
|
|
1722
|
+
for (const item of store.items) {
|
|
1723
|
+
if (item.title) continue;
|
|
1724
|
+
const pack = decoded.get(String(item.sessionId));
|
|
1725
|
+
const t = pack && pack.meta && pack.meta.title;
|
|
1726
|
+
if (t) item.title = String(t);
|
|
1727
|
+
}
|
|
1728
|
+
});
|
|
1729
|
+
} catch (e) {
|
|
1730
|
+
}
|
|
1656
1731
|
}
|
|
1657
1732
|
async function runWarm() {
|
|
1658
1733
|
if (warmRunning) return;
|
|
@@ -1663,21 +1738,27 @@ function apply(ctx) {
|
|
|
1663
1738
|
for (const [id] of batch) warmQueue.delete(id);
|
|
1664
1739
|
try {
|
|
1665
1740
|
const ids = batch.map(([id]) => id);
|
|
1666
|
-
const
|
|
1741
|
+
const resultById = await projectTitlesStatus(ids);
|
|
1667
1742
|
const decoded = /* @__PURE__ */ new Map();
|
|
1668
1743
|
const statsById = /* @__PURE__ */ new Map();
|
|
1669
1744
|
for (const [id, desc] of batch) {
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1745
|
+
const r = resultById.get(id);
|
|
1746
|
+
if (!r || !r.resolved) {
|
|
1747
|
+
warmMarkFailure(id, desc.stat, desc.header);
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
warmFail.delete(id);
|
|
1751
|
+
const meta = metaFromSnapshot(r.snapshot || null);
|
|
1672
1752
|
if (desc.header) {
|
|
1673
1753
|
if (typeof desc.header.cwd === "string") meta.cwd = desc.header.cwd;
|
|
1674
1754
|
if (desc.header.createdAt != null) meta.createdAt = desc.header.createdAt;
|
|
1675
1755
|
}
|
|
1676
1756
|
metaCache.set(id, desc.stat, meta);
|
|
1677
1757
|
statsById.set(id, desc.stat);
|
|
1678
|
-
|
|
1758
|
+
decoded.set(id, { meta, resolved: true });
|
|
1679
1759
|
}
|
|
1680
1760
|
await persistDecoded(decoded, statsById);
|
|
1761
|
+
await backfillTrashTitles(decoded);
|
|
1681
1762
|
} catch (e) {
|
|
1682
1763
|
}
|
|
1683
1764
|
await new Promise((resolve) => setImmediate(resolve));
|
|
@@ -1685,6 +1766,7 @@ function apply(ctx) {
|
|
|
1685
1766
|
} finally {
|
|
1686
1767
|
warmRunning = false;
|
|
1687
1768
|
if (warmQueue.size) scheduleWarm();
|
|
1769
|
+
scheduleWarmRetry();
|
|
1688
1770
|
}
|
|
1689
1771
|
}
|
|
1690
1772
|
async function collectUsage(preloadedEntries) {
|
|
@@ -1804,30 +1886,19 @@ function apply(ctx) {
|
|
|
1804
1886
|
cwd = header.cwd || null;
|
|
1805
1887
|
title = header.title || header.meta && header.meta.title || null;
|
|
1806
1888
|
}
|
|
1807
|
-
if (!title) {
|
|
1808
|
-
if (typeof sq.readTitleSnapshot === "function") {
|
|
1809
|
-
try {
|
|
1810
|
-
const snap = unwrapSnapshot(await sq.readTitleSnapshot(sid));
|
|
1811
|
-
if (snap && snap.title && snap.title.title) title = String(snap.title.title);
|
|
1812
|
-
if (snap && snap.session) {
|
|
1813
|
-
if (!cwd) cwd = snap.session.cwd || null;
|
|
1814
|
-
}
|
|
1815
|
-
} catch (e) {
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
1889
|
if (!title) {
|
|
1820
1890
|
try {
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
});
|
|
1827
|
-
if (summary && summary.meta && !cwd) cwd = summary.meta.cwd || null;
|
|
1828
|
-
title = folded;
|
|
1891
|
+
const delStat = persistenceEntry && typeof persistenceEntry.revision === "string" && persistenceEntry.revision ? { revision: persistenceEntry.revision, sizeBytes: Number.isFinite(persistenceEntry.sizeBytes) ? Number(persistenceEntry.sizeBytes) : void 0 } : removedPath ? await stat4(removedPath).then((st) => ({ mtimeMs: st.mtimeMs, size: st.size })) : null;
|
|
1892
|
+
if (delStat) {
|
|
1893
|
+
const m = metaCache.get(sid, delStat) || (await hydrateFromPersist([sid], /* @__PURE__ */ new Map([[sid, delStat]]))).get(sid);
|
|
1894
|
+
if (m && m.title) title = String(m.title);
|
|
1895
|
+
}
|
|
1829
1896
|
} catch (e) {
|
|
1830
1897
|
}
|
|
1898
|
+
if (!title) {
|
|
1899
|
+
const at = authorityTitleCache.get(sid);
|
|
1900
|
+
if (at) title = String(at);
|
|
1901
|
+
}
|
|
1831
1902
|
}
|
|
1832
1903
|
} catch (e) {
|
|
1833
1904
|
}
|
|
@@ -1839,8 +1910,10 @@ function apply(ctx) {
|
|
|
1839
1910
|
const archived = await mutateArchived((list) => ({ next: null, value: list.includes(sid) })).catch(() => false);
|
|
1840
1911
|
await mutateTrash((store) => {
|
|
1841
1912
|
const entry = {
|
|
1913
|
+
// v3.6.2 #7:不再把 cwd/裸 id 冒充标题永久定格——存 null,预热解出后
|
|
1914
|
+
// backfillTrashTitles 补写;渲染端(t.title || t.sessionId)自然降级。
|
|
1842
1915
|
sessionId: sid,
|
|
1843
|
-
title: title ||
|
|
1916
|
+
title: title || null,
|
|
1844
1917
|
cwd: cwd || null,
|
|
1845
1918
|
header: header || null,
|
|
1846
1919
|
originalPath: removedPath || null,
|
|
@@ -2455,19 +2528,50 @@ function apply(ctx) {
|
|
|
2455
2528
|
return { next: null, value: { ok: true, archived: true } };
|
|
2456
2529
|
});
|
|
2457
2530
|
}
|
|
2458
|
-
|
|
2531
|
+
const warmHasApi = typeof sq.readTitleSnapshots === "function" || typeof sq.readTitleSnapshot === "function";
|
|
2532
|
+
async function projectTitlesStatus(ids) {
|
|
2459
2533
|
const out = /* @__PURE__ */ new Map();
|
|
2534
|
+
const markAll = (resolved) => {
|
|
2535
|
+
for (const id of ids) out.set(String(id), { resolved, snapshot: null });
|
|
2536
|
+
};
|
|
2460
2537
|
if (!ids || !ids.length) return out;
|
|
2461
|
-
if (
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2538
|
+
if (!warmHasApi) {
|
|
2539
|
+
markAll(false);
|
|
2540
|
+
return out;
|
|
2541
|
+
}
|
|
2542
|
+
if (typeof sq.readTitleSnapshots === "function") {
|
|
2543
|
+
let results = null;
|
|
2544
|
+
try {
|
|
2545
|
+
results = await sq.readTitleSnapshots(ids);
|
|
2546
|
+
} catch (e) {
|
|
2547
|
+
results = null;
|
|
2548
|
+
}
|
|
2549
|
+
if (Array.isArray(results)) {
|
|
2550
|
+
results.forEach((result, index) => {
|
|
2551
|
+
const id = String(ids[index]);
|
|
2552
|
+
if (result && result.status === "fulfilled") out.set(id, { resolved: true, snapshot: unwrapSnapshot(result) });
|
|
2553
|
+
else if (result && result.status === "rejected") out.set(id, { resolved: false, snapshot: null });
|
|
2554
|
+
else if (result) out.set(id, { resolved: true, snapshot: unwrapSnapshot(result) });
|
|
2555
|
+
else out.set(id, { resolved: false, snapshot: null });
|
|
2556
|
+
});
|
|
2557
|
+
for (const id of ids) if (!out.has(String(id))) out.set(String(id), { resolved: false, snapshot: null });
|
|
2558
|
+
return out;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (typeof sq.readTitleSnapshot === "function") {
|
|
2562
|
+
for (const raw of ids) {
|
|
2563
|
+
const id = String(raw);
|
|
2564
|
+
try {
|
|
2565
|
+
const r = await sq.readTitleSnapshot(id);
|
|
2566
|
+
if (r && r.status === "rejected") out.set(id, { resolved: false, snapshot: null });
|
|
2567
|
+
else out.set(id, { resolved: true, snapshot: unwrapSnapshot(r) });
|
|
2568
|
+
} catch (e) {
|
|
2569
|
+
out.set(id, { resolved: false, snapshot: null });
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
return out;
|
|
2470
2573
|
}
|
|
2574
|
+
markAll(false);
|
|
2471
2575
|
return out;
|
|
2472
2576
|
}
|
|
2473
2577
|
async function allSessionItemsDetailed(opts = {}) {
|
|
@@ -2662,40 +2766,53 @@ function apply(ctx) {
|
|
|
2662
2766
|
titles: Object.fromEntries(authorityTitleCache),
|
|
2663
2767
|
trashedSessionIds: store.items.map((item) => String(item.sessionId)),
|
|
2664
2768
|
purgedSessionIds: activeTombstones,
|
|
2665
|
-
lineage
|
|
2769
|
+
lineage,
|
|
2770
|
+
// v3.6.2:还有预热/退避重试在途 → client 缩短轮询节拍,预热一完成就把
|
|
2771
|
+
// 补齐的标题送回(含面板自动刷新)。无预热 API 时恒 false,绝不假忙。
|
|
2772
|
+
warmPending: warmPendingNow()
|
|
2666
2773
|
};
|
|
2667
2774
|
}
|
|
2668
2775
|
const emptyScanCache = /* @__PURE__ */ new Map();
|
|
2776
|
+
const REFINE_BUDGET = 40;
|
|
2777
|
+
function applyEmptyScan(lineage, id, empty) {
|
|
2778
|
+
if (empty) {
|
|
2779
|
+
const info = lineage[id] || (lineage[id] = { origin: null, parentSession: null, delegationDepth: 0, empty: false });
|
|
2780
|
+
info.empty = true;
|
|
2781
|
+
} else if (lineage[id]) {
|
|
2782
|
+
const info = lineage[id];
|
|
2783
|
+
info.empty = false;
|
|
2784
|
+
if (!info.origin && !info.parentSession) delete lineage[id];
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2669
2787
|
async function refineEmptyLineage(lineage, entries) {
|
|
2670
2788
|
if (!persistence || typeof persistence.inspectSession !== "function") return;
|
|
2789
|
+
const limiter = createLimiter(4);
|
|
2790
|
+
const todo = [];
|
|
2671
2791
|
for (const entry of entries) {
|
|
2672
2792
|
const size = entry && Number.isFinite(entry.sizeBytes) ? entry.sizeBytes : null;
|
|
2673
2793
|
if (size === null || size > EMPTY_DECODE_LIMIT) continue;
|
|
2674
2794
|
const id = String(entry.id);
|
|
2675
|
-
|
|
2676
|
-
if (
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
const types = [];
|
|
2680
|
-
await persistence.inspectSession(id, { onEvents: (batch) => {
|
|
2681
|
-
for (const ev of batch || []) if (types.length < 64) types.push(ev && ev.type);
|
|
2682
|
-
} });
|
|
2683
|
-
isEmpty = isEmptyEventTypes(types);
|
|
2684
|
-
} catch (e) {
|
|
2685
|
-
isEmpty = false;
|
|
2686
|
-
}
|
|
2687
|
-
scanned = { sizeBytes: size, empty: isEmpty };
|
|
2688
|
-
emptyScanCache.set(id, scanned);
|
|
2689
|
-
}
|
|
2690
|
-
if (scanned.empty) {
|
|
2691
|
-
const info = lineage[id] || (lineage[id] = { origin: null, parentSession: null, delegationDepth: 0, empty: false });
|
|
2692
|
-
info.empty = true;
|
|
2693
|
-
} else if (lineage[id]) {
|
|
2694
|
-
const info = lineage[id];
|
|
2695
|
-
info.empty = false;
|
|
2696
|
-
if (!info.origin && !info.parentSession) delete lineage[id];
|
|
2795
|
+
const scanned = emptyScanCache.get(id);
|
|
2796
|
+
if (scanned && scanned.sizeBytes === size) {
|
|
2797
|
+
applyEmptyScan(lineage, id, scanned.empty);
|
|
2798
|
+
continue;
|
|
2697
2799
|
}
|
|
2800
|
+
if (todo.length < REFINE_BUDGET) todo.push({ id, size });
|
|
2698
2801
|
}
|
|
2802
|
+
await Promise.all(todo.map(({ id, size }) => limiter(async () => {
|
|
2803
|
+
let isEmpty = false;
|
|
2804
|
+
try {
|
|
2805
|
+
const types = [];
|
|
2806
|
+
await persistence.inspectSession(id, { onEvents: (batch) => {
|
|
2807
|
+
for (const ev of batch || []) if (types.length < 64) types.push(ev && ev.type);
|
|
2808
|
+
} });
|
|
2809
|
+
isEmpty = isEmptyEventTypes(types);
|
|
2810
|
+
} catch (e) {
|
|
2811
|
+
isEmpty = false;
|
|
2812
|
+
}
|
|
2813
|
+
emptyScanCache.set(id, { sizeBytes: size, empty: isEmpty });
|
|
2814
|
+
applyEmptyScan(lineage, id, isEmpty);
|
|
2815
|
+
})));
|
|
2699
2816
|
const live = /* @__PURE__ */ new Set();
|
|
2700
2817
|
for (const entry of entries) {
|
|
2701
2818
|
const size = entry && Number.isFinite(entry.sizeBytes) ? entry.sizeBytes : null;
|
|
@@ -3094,7 +3211,7 @@ function apply(ctx) {
|
|
|
3094
3211
|
path: "/archived-sessions/sessions",
|
|
3095
3212
|
handler: async (req, res) => {
|
|
3096
3213
|
try {
|
|
3097
|
-
json(res, { items: await allSessionItems() });
|
|
3214
|
+
json(res, { items: await allSessionItems(), warmPending: warmPendingNow() });
|
|
3098
3215
|
} catch (e) {
|
|
3099
3216
|
json(res, { error: String(e && e.message || e) }, 500);
|
|
3100
3217
|
}
|
|
@@ -3198,9 +3315,18 @@ function apply(ctx) {
|
|
|
3198
3315
|
});
|
|
3199
3316
|
if (Number.isFinite(sizeBytes)) sizeById.set(id, sizeBytes);
|
|
3200
3317
|
};
|
|
3318
|
+
const lineageStats = /* @__PURE__ */ new Map();
|
|
3319
|
+
const lineageHeaders = /* @__PURE__ */ new Map();
|
|
3201
3320
|
try {
|
|
3202
3321
|
for (const entry of await persistence.listEntries()) {
|
|
3203
3322
|
addHeader(entry.header, entry.sizeBytes);
|
|
3323
|
+
const eid = entry && entry.id != null ? String(entry.id) : null;
|
|
3324
|
+
if (eid) {
|
|
3325
|
+
lineageHeaders.set(eid, { header: entry && entry.header || null });
|
|
3326
|
+
if (entry && typeof entry.revision === "string" && entry.revision) {
|
|
3327
|
+
lineageStats.set(eid, Number.isFinite(entry.sizeBytes) ? { revision: entry.revision, sizeBytes: Number(entry.sizeBytes) } : { revision: entry.revision });
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3204
3330
|
if (entry && entry.id != null) persistedIds.add(String(entry.id));
|
|
3205
3331
|
}
|
|
3206
3332
|
} catch (e) {
|
|
@@ -3252,11 +3378,17 @@ function apply(ctx) {
|
|
|
3252
3378
|
n.children.forEach(walk);
|
|
3253
3379
|
};
|
|
3254
3380
|
nodes.forEach(walk);
|
|
3255
|
-
const
|
|
3381
|
+
const lineageIds = allIds.map(String);
|
|
3382
|
+
const { cached: lineageCached, missing: lineageMissing } = metaCache.partition(lineageIds, lineageStats);
|
|
3383
|
+
const lineagePersisted = await hydrateFromPersist(lineageMissing, lineageStats);
|
|
3384
|
+
for (const [id, meta] of lineagePersisted) metaCache.set(id, lineageStats.get(id), meta);
|
|
3385
|
+
const lineageRest = lineageMissing.filter((id) => !lineagePersisted.has(id));
|
|
3386
|
+
if (lineageRest.length) enqueueWarm(lineageRest, lineageStats, lineageHeaders);
|
|
3387
|
+
const truncateTitle = (t) => t ? t.length > MAX_TITLE ? t.slice(0, MAX_TITLE) + "\u2026" : t : null;
|
|
3256
3388
|
const titleOf = (id) => {
|
|
3257
|
-
const
|
|
3258
|
-
const m =
|
|
3259
|
-
return m && m.title || null;
|
|
3389
|
+
const key = String(id);
|
|
3390
|
+
const m = lineageCached.get(key) || lineagePersisted.get(key);
|
|
3391
|
+
return truncateTitle(m && m.title || authorityTitleCache.get(key) || null);
|
|
3260
3392
|
};
|
|
3261
3393
|
const fill = (n) => {
|
|
3262
3394
|
n.title = titleOf(n.sessionId);
|