dsh-sessions-manager 3.6.0 → 3.6.1

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 CHANGED
@@ -154,7 +154,7 @@ lib/index.js pre-built host (ESM)
154
154
  lib/client.js pre-built client (ModuleLoader CJS handshake)
155
155
  ```
156
156
 
157
- `lib/` is pre-built, so cloning and using it needs no esbuild. To modify the source, run `npm i -D esbuild && npm run build` to regenerate `lib/`.
157
+ `lib/` is pre-built, so cloning and using it needs no esbuild. To modify the source, run `npm i -D esbuild && npm run build` to regenerate `lib/`; before committing, `npm run check:dist` rebuilds and diffs `lib/` against the committed bundles (CI runs the same check), so **always rebuild after a version bump** — the build stamp is reproducible as `vX.Y.Z+source-hash`, which is what makes that comparison exact.
158
158
 
159
159
  ## Host routes
160
160
 
package/README.md CHANGED
@@ -154,7 +154,7 @@ lib/index.js 预构建 host(ESM)
154
154
  lib/client.js 预构建 client(ModuleLoader CJS handshake)
155
155
  ```
156
156
 
157
- `lib/` 已预构建,clone 下来即可直接用、无需 esbuild。若要改源码,运行 `npm i -D esbuild && npm run build` 重新生成 `lib/`。
157
+ `lib/` 已预构建,clone 下来即可直接用、无需 esbuild。若要改源码,运行 `npm i -D esbuild && npm run build` 重新生成 `lib/`;提交前用 `npm run check:dist` 重建并与仓库产物比对(CI 跑同一道检查),**改版本号后务必一并重建**——构建指纹可复现为 `vX.Y.Z+源码哈希`,正是为了让这道比对精确可靠。
158
158
 
159
159
  ## Host 路由
160
160
 
package/lib/index.js CHANGED
@@ -614,6 +614,12 @@ function fingerprintOf(stat5) {
614
614
  function isPersistableFingerprint(fingerprint) {
615
615
  return typeof fingerprint === "string" && fingerprint !== "" && !fingerprint.startsWith(REVISION_PREFIX);
616
616
  }
617
+ function persistFingerprintOf(stat5) {
618
+ const fp = fingerprintOf(stat5);
619
+ if (fp && isPersistableFingerprint(fp)) return fp;
620
+ if (stat5 && Number.isFinite(stat5.sizeBytes) && stat5.sizeBytes >= 0) return `sz:${stat5.sizeBytes}`;
621
+ return null;
622
+ }
617
623
  function isFresh(entry, stat5, now, ttlMs = DEFAULT_TTL_MS) {
618
624
  if (!entry) return false;
619
625
  const fp = fingerprintOf(stat5);
@@ -1408,7 +1414,7 @@ function classifyLineage(header, sizeBytes) {
1408
1414
  }
1409
1415
 
1410
1416
  // src/index.js
1411
- var BUILD_STAMP = true ? "3.6.0 built 2026-09-10T09:47:49.428Z" : "dev";
1417
+ var BUILD_STAMP = true ? "3.6.1+f0814a9e" : "dev";
1412
1418
  var name = "dsh-sessions-manager";
1413
1419
  var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
1414
1420
  var MAX_TITLE = 80;
@@ -1530,8 +1536,8 @@ function apply(ctx) {
1530
1536
  const stat5 = statsById.get(id);
1531
1537
  const entry = store && store[id];
1532
1538
  if (!stat5 || !entry) continue;
1533
- const fp = fingerprintOf(stat5);
1534
- if (!isPersistableFingerprint(fp)) continue;
1539
+ const fp = persistFingerprintOf(stat5);
1540
+ if (!fp) continue;
1535
1541
  if (fp && entry.fingerprint === fp) {
1536
1542
  hits.set(id, { title: entry.title, cwd: entry.cwd, createdAt: entry.createdAt });
1537
1543
  }
@@ -1543,8 +1549,8 @@ function apply(ctx) {
1543
1549
  const batch = {};
1544
1550
  const now = Date.now();
1545
1551
  for (const [id, meta] of decoded) {
1546
- const fp = fingerprintOf(statsById.get(id));
1547
- if (!isPersistableFingerprint(fp)) continue;
1552
+ const fp = persistFingerprintOf(statsById.get(id));
1553
+ if (!fp) continue;
1548
1554
  batch[id] = { title: meta.title, cwd: meta.cwd, createdAt: meta.createdAt, fingerprint: fp, updatedAt: now };
1549
1555
  }
1550
1556
  if (!Object.keys(batch).length) return;
@@ -1623,50 +1629,63 @@ function apply(ctx) {
1623
1629
  }
1624
1630
  return base;
1625
1631
  }
1626
- async function resolveOne(id, usage, opts = {}) {
1627
- const key = String(id);
1628
- const statInfo = usage ? (usage.statsById ? usage.statsById.get(key) : null) || { mtimeMs: usage.mtimeById && usage.mtimeById.get(key), size: usage.sizeById && usage.sizeById.get(key) } : null;
1629
- const cached = metaCache.get(key, statInfo);
1630
- if (cached) return buildItem(key, cached, usage, opts.exposeUsage);
1631
- let meta = { title: null, cwd: null, createdAt: null };
1632
- if (opts.listHeader) {
1633
- if (typeof opts.listHeader.cwd === "string") meta.cwd = opts.listHeader.cwd;
1634
- if (opts.listHeader.createdAt != null) meta.createdAt = opts.listHeader.createdAt;
1635
- }
1636
- if (opts.preloaded !== void 0) {
1637
- const projected = metaFromSnapshot(unwrapSnapshot(opts.preloaded));
1638
- if (projected.title) meta.title = projected.title;
1639
- if (!meta.cwd && projected.cwd) meta.cwd = projected.cwd;
1640
- if (!meta.createdAt && projected.createdAt) meta.createdAt = projected.createdAt;
1641
- } else if (typeof sq.readTitleSnapshot === "function") {
1642
- try {
1643
- const projected = metaFromSnapshot(await sq.readTitleSnapshot(id));
1644
- if (projected.title) meta.title = projected.title;
1645
- if (!meta.cwd && projected.cwd) meta.cwd = projected.cwd;
1646
- if (!meta.createdAt && projected.createdAt) meta.createdAt = projected.createdAt;
1647
- } catch (e) {
1648
- }
1632
+ const EMPTY_META = { title: null, cwd: null, createdAt: null };
1633
+ const WARM_CHUNK = 4;
1634
+ const warmQueue = /* @__PURE__ */ new Map();
1635
+ let warmRunning = false;
1636
+ let warmKickTimer = null;
1637
+ function scheduleWarm() {
1638
+ if (warmKickTimer || warmRunning) return;
1639
+ warmKickTimer = setTimeout(() => {
1640
+ warmKickTimer = null;
1641
+ runWarm();
1642
+ }, 25);
1643
+ if (typeof warmKickTimer.unref === "function") warmKickTimer.unref();
1644
+ }
1645
+ function enqueueWarm(ids, statsById, entryById) {
1646
+ if (!ids || !ids.length) return;
1647
+ for (const id of ids) {
1648
+ const key = String(id);
1649
+ const entry = entryById ? entryById.get(key) : null;
1650
+ warmQueue.set(key, {
1651
+ stat: statsById ? statsById.get(key) || null : null,
1652
+ header: entry && entry.header || null
1653
+ });
1649
1654
  }
1650
- const projectionAvailable = typeof sq.readTitleSnapshot === "function" || typeof sq.readTitleSnapshots === "function";
1651
- if (!meta.cwd || !meta.title && !projectionAvailable) {
1652
- try {
1653
- let foldedTitle = null;
1654
- const summary = await persistence.inspectSession(key, {
1655
- onEvents: (events) => {
1656
- if (!foldedTitle) foldedTitle = foldTitle(events);
1655
+ scheduleWarm();
1656
+ }
1657
+ async function runWarm() {
1658
+ if (warmRunning) return;
1659
+ warmRunning = true;
1660
+ try {
1661
+ while (warmQueue.size) {
1662
+ const batch = [...warmQueue.entries()].slice(0, WARM_CHUNK);
1663
+ for (const [id] of batch) warmQueue.delete(id);
1664
+ try {
1665
+ const ids = batch.map(([id]) => id);
1666
+ const snapshotById = await projectTitles(ids);
1667
+ const decoded = /* @__PURE__ */ new Map();
1668
+ const statsById = /* @__PURE__ */ new Map();
1669
+ for (const [id, desc] of batch) {
1670
+ const snapshot = snapshotById.has(id) ? snapshotById.get(id) : null;
1671
+ const meta = metaFromSnapshot(snapshot);
1672
+ if (desc.header) {
1673
+ if (typeof desc.header.cwd === "string") meta.cwd = desc.header.cwd;
1674
+ if (desc.header.createdAt != null) meta.createdAt = desc.header.createdAt;
1675
+ }
1676
+ metaCache.set(id, desc.stat, meta);
1677
+ statsById.set(id, desc.stat);
1678
+ if (desc.stat) decoded.set(id, meta);
1657
1679
  }
1658
- });
1659
- if (summary && summary.meta) {
1660
- if (!meta.cwd) meta.cwd = summary.meta.cwd || null;
1661
- if (!meta.createdAt) meta.createdAt = summary.meta.createdAt || null;
1680
+ await persistDecoded(decoded, statsById);
1681
+ } catch (e) {
1662
1682
  }
1663
- if (!meta.title && foldedTitle) meta.title = foldedTitle;
1664
- } catch (e2) {
1683
+ await new Promise((resolve) => setImmediate(resolve));
1665
1684
  }
1685
+ } finally {
1686
+ warmRunning = false;
1687
+ if (warmQueue.size) scheduleWarm();
1666
1688
  }
1667
- metaCache.set(key, statInfo, meta);
1668
- if (opts.collectDecoded && statInfo) opts.collectDecoded(key, meta);
1669
- return buildItem(key, meta, usage, opts.exposeUsage);
1670
1689
  }
1671
1690
  async function collectUsage(preloadedEntries) {
1672
1691
  const sizeById = /* @__PURE__ */ new Map();
@@ -1690,7 +1709,7 @@ function apply(ctx) {
1690
1709
  if (!id) return;
1691
1710
  if (entry && typeof entry.revision === "string" && entry.revision) {
1692
1711
  if (Number.isFinite(entry.sizeBytes)) sizeById.set(id, Number(entry.sizeBytes));
1693
- statsById.set(id, { revision: entry.revision });
1712
+ statsById.set(id, Number.isFinite(entry.sizeBytes) ? { revision: entry.revision, sizeBytes: Number(entry.sizeBytes) } : { revision: entry.revision });
1694
1713
  return;
1695
1714
  }
1696
1715
  if (entry && Number.isFinite(entry.sizeBytes)) sizeById.set(id, Number(entry.sizeBytes));
@@ -2491,35 +2510,48 @@ function apply(ctx) {
2491
2510
  wsByPath = {};
2492
2511
  }
2493
2512
  const currentArchived = new Set((await archivedState().catch(() => ({ archivedSessionIds: [] }))).archivedSessionIds || []);
2494
- const items = [];
2495
2513
  const usage = await collectUsage(entries);
2496
2514
  const statsById = new Map(visibleIds.map((id) => [
2497
2515
  id,
2498
2516
  usage.statsById && usage.statsById.get(id) || { mtimeMs: usage.mtimeById.get(id), size: usage.sizeById.get(id) }
2499
2517
  ]));
2500
- const { missing } = metaCache.partition(visibleIds, statsById);
2518
+ const { cached, missing } = metaCache.partition(visibleIds, statsById);
2501
2519
  const persisted = await hydrateFromPersist(missing, statsById);
2502
- for (const [id, meta] of persisted) metaCache.set(id, statsById.get(id), meta);
2520
+ for (const [id, meta] of persisted) {
2521
+ const entry = entryById.get(id);
2522
+ const header = entry && entry.header ? entry.header : null;
2523
+ metaCache.set(id, statsById.get(id), {
2524
+ title: meta.title,
2525
+ cwd: header && typeof header.cwd === "string" && header.cwd ? header.cwd : meta.cwd,
2526
+ createdAt: header && header.createdAt != null ? header.createdAt : meta.createdAt
2527
+ });
2528
+ }
2503
2529
  const stillMissing = missing.filter((id) => !persisted.has(id));
2504
- const snapshotById = await projectTitles(stillMissing);
2505
- const decoded = /* @__PURE__ */ new Map();
2506
- const collectDecoded = (id, meta) => {
2507
- decoded.set(id, meta);
2508
- };
2509
- const CHUNK = 6;
2510
- for (let i = 0; i < visibleIds.length; i += CHUNK) {
2511
- const res2 = await Promise.all(visibleIds.slice(i, i + CHUNK).map((id) => {
2530
+ if (stillMissing.length) enqueueWarm(stillMissing, statsById, entryById);
2531
+ const items = [];
2532
+ for (const id of visibleIds) {
2533
+ let meta = metaCache.get(id, statsById.get(id)) || persisted.get(id) || null;
2534
+ if (!meta) {
2535
+ meta = { title: null, cwd: null, createdAt: null };
2512
2536
  const entry = entryById.get(id);
2513
- return resolveOne(id, usage, {
2514
- exposeUsage: !!(opts && opts.usage),
2515
- listHeader: entry ? entry.header : null,
2516
- preloaded: snapshotById.has(id) ? snapshotById.get(id) : void 0,
2517
- collectDecoded
2518
- });
2519
- }));
2520
- for (const it of res2) items.push({ ...it, archived: currentArchived.has(it.sessionId) });
2537
+ const header = entry && entry.header ? entry.header : null;
2538
+ if (header) {
2539
+ if (typeof header.cwd === "string") meta.cwd = header.cwd;
2540
+ if (header.createdAt != null) meta.createdAt = header.createdAt;
2541
+ }
2542
+ }
2543
+ const it = buildItem(id, meta, usage, !!(opts && opts.usage));
2544
+ items.push({ ...it, archived: currentArchived.has(it.sessionId) });
2545
+ }
2546
+ if (opts && opts.onlyArchived) {
2547
+ const archivedItems = [];
2548
+ for (const it of items) {
2549
+ if (!it.archived) continue;
2550
+ const { archived: _drop, ...rest } = it;
2551
+ archivedItems.push(rest);
2552
+ }
2553
+ return { items: archivedItems, usage };
2521
2554
  }
2522
- await persistDecoded(decoded, statsById);
2523
2555
  let starredSet = /* @__PURE__ */ new Set();
2524
2556
  try {
2525
2557
  starredSet = new Set((await stars.read()).starredSessionIds);
@@ -2597,36 +2629,28 @@ function apply(ctx) {
2597
2629
  const activeTombstones = store.purgedSessionIds.map(String).filter((id) => !present.has(id));
2598
2630
  if (ids.length) {
2599
2631
  const usage = await collectUsage(entries);
2632
+ const entryById = new Map(entries.map((entry) => [entry.id, entry]));
2600
2633
  const statsById = new Map(ids.map((id) => [
2601
2634
  id,
2602
2635
  usage.statsById && usage.statsById.get(id) || { mtimeMs: usage.mtimeById.get(id), size: usage.sizeById.get(id) }
2603
2636
  ]));
2604
2637
  const { cached, missing } = metaCache.partition(ids, statsById);
2605
2638
  const persisted = await hydrateFromPersist(missing, statsById);
2606
- for (const [id, meta] of persisted) metaCache.set(id, statsById.get(id), meta);
2639
+ for (const [id, meta] of persisted) {
2640
+ const entry = entryById.get(id);
2641
+ const header = entry && entry.header ? entry.header : null;
2642
+ metaCache.set(id, statsById.get(id), {
2643
+ title: meta.title,
2644
+ cwd: header && typeof header.cwd === "string" && header.cwd ? header.cwd : meta.cwd,
2645
+ createdAt: header && header.createdAt != null ? header.createdAt : meta.createdAt
2646
+ });
2647
+ }
2607
2648
  const rest = missing.filter((id) => !persisted.has(id));
2608
- const snapshotById = await projectTitles(rest);
2609
- const decoded = /* @__PURE__ */ new Map();
2610
- const collectDecoded = (id, meta) => {
2611
- decoded.set(id, meta);
2612
- };
2649
+ if (rest.length) enqueueWarm(rest, statsById, entryById);
2613
2650
  for (const id of ids) {
2614
- let meta = cached.get(id) || persisted.get(id) || null;
2615
- if (!meta) {
2616
- const entry = entries.find((e) => e.id === id);
2617
- const snapshot = snapshotById.has(id) ? snapshotById.get(id) : typeof sq.readTitleSnapshot === "function" ? await sq.readTitleSnapshot(id).catch(() => null) : null;
2618
- const next = metaFromSnapshot(snapshot);
2619
- if (entry && entry.header) {
2620
- if (!next.cwd && typeof entry.header.cwd === "string") next.cwd = entry.header.cwd;
2621
- if (!next.createdAt && entry.header.createdAt != null) next.createdAt = entry.header.createdAt;
2622
- }
2623
- metaCache.set(id, statsById.get(id), next);
2624
- if (statsById.get(id)) collectDecoded(id, next);
2625
- meta = next;
2626
- }
2651
+ const meta = metaCache.get(id, statsById.get(id)) || persisted.get(id) || null;
2627
2652
  if (meta && meta.title) authorityTitleCache.set(id, String(meta.title));
2628
2653
  }
2629
- await persistDecoded(decoded, statsById);
2630
2654
  }
2631
2655
  const lineage = {};
2632
2656
  for (const entry of entries) {
@@ -2866,12 +2890,10 @@ function apply(ctx) {
2866
2890
  } catch (e) {
2867
2891
  wsByPath = {};
2868
2892
  }
2869
- const items = [];
2870
- const CHUNK = 6;
2871
- for (let i = 0; i < idStrs.length; i += CHUNK) {
2872
- const res2 = await Promise.all(idStrs.slice(i, i + CHUNK).map((id) => resolveOne(id)));
2873
- items.push.apply(items, res2);
2874
- }
2893
+ const wanted = new Set(idStrs);
2894
+ const detailed = await allSessionItemsDetailed();
2895
+ const items = detailed.items.filter((it) => wanted.has(String(it.sessionId)) && it.archived);
2896
+ for (const it of items) delete it.archived;
2875
2897
  json(res, { items });
2876
2898
  } catch (e) {
2877
2899
  json(res, { error: String(e && e.message || e) }, 500);