dsh-sessions-manager 3.7.2 → 3.7.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/lib/index.js CHANGED
@@ -960,7 +960,9 @@ function pickInactiveCandidates(items, options = {}) {
960
960
  const now = Number.isFinite(options.now) ? options.now : Date.now();
961
961
  const cutoff = now - days * DAY_MS;
962
962
  const skipStarred = options.skipStarred !== false;
963
- const activeId = options.activeSessionId != null ? String(options.activeSessionId) : null;
963
+ const activeIds = new Set(
964
+ Array.isArray(options.activeSessionIds) ? options.activeSessionIds.map((id) => String(id)) : options.activeSessionId != null ? [String(options.activeSessionId)] : []
965
+ );
964
966
  const list = Array.isArray(items) ? items : [];
965
967
  const out = [];
966
968
  const seen = /* @__PURE__ */ new Set();
@@ -970,7 +972,7 @@ function pickInactiveCandidates(items, options = {}) {
970
972
  if (seen.has(id)) continue;
971
973
  if (item.archived) continue;
972
974
  if (skipStarred && item.starred) continue;
973
- if (activeId !== null && id === activeId) continue;
975
+ if (activeIds.has(id)) continue;
974
976
  const updatedAt = Number(item.updatedAt);
975
977
  if (!Number.isFinite(updatedAt) || updatedAt <= 0) continue;
976
978
  if (updatedAt < cutoff) {
@@ -1350,6 +1352,58 @@ function createEmptyScanStore({ dir, file }) {
1350
1352
  };
1351
1353
  }
1352
1354
 
1355
+ // src/active-session.js
1356
+ var DEFAULT_ACTIVE_TTL_MS = 45 * 1e3;
1357
+ var MAX_ACTIVE_REPORTS = 32;
1358
+ function isUsableSessionId(value) {
1359
+ return typeof value === "string" && value.length > 0 && value.length <= 200 && !/[\\/\0]/.test(value) && value !== "." && value !== "..";
1360
+ }
1361
+ function createActiveSessionTracker(options = {}) {
1362
+ const ttl = Number.isFinite(options.ttlMs) ? Number(options.ttlMs) : DEFAULT_ACTIVE_TTL_MS;
1363
+ const now = typeof options.now === "function" ? options.now : () => Date.now();
1364
+ const reports = /* @__PURE__ */ new Map();
1365
+ function trim(at) {
1366
+ for (const [key, value] of reports) if (at - value.at > ttl) reports.delete(key);
1367
+ while (reports.size > MAX_ACTIVE_REPORTS) {
1368
+ const oldest = reports.keys().next().value;
1369
+ if (oldest === void 0) break;
1370
+ reports.delete(oldest);
1371
+ }
1372
+ }
1373
+ function report(clientId, sessionId, at = now()) {
1374
+ const key = typeof clientId === "string" && clientId ? clientId.slice(0, 64) : "default";
1375
+ if (sessionId === null || sessionId === void 0 || sessionId === "") {
1376
+ reports.delete(key);
1377
+ return true;
1378
+ }
1379
+ if (!isUsableSessionId(sessionId)) return false;
1380
+ reports.set(key, { id: String(sessionId), at });
1381
+ trim(at);
1382
+ return true;
1383
+ }
1384
+ function activeIds(at = now()) {
1385
+ const out = /* @__PURE__ */ new Set();
1386
+ for (const [key, value] of reports) {
1387
+ if (at - value.at > ttl) {
1388
+ reports.delete(key);
1389
+ continue;
1390
+ }
1391
+ out.add(value.id);
1392
+ }
1393
+ return out;
1394
+ }
1395
+ return {
1396
+ report,
1397
+ activeIds,
1398
+ get size() {
1399
+ return reports.size;
1400
+ },
1401
+ clear() {
1402
+ reports.clear();
1403
+ }
1404
+ };
1405
+ }
1406
+
1353
1407
  // src/handle-era-paths.js
1354
1408
  import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
1355
1409
  import { basename, join as join10 } from "node:path";
@@ -1985,7 +2039,7 @@ function emptyScanCandidate(sizeBytes) {
1985
2039
  }
1986
2040
 
1987
2041
  // src/index.js
1988
- var BUILD_STAMP = true ? "3.7.2+71b956b8" : "dev";
2042
+ var BUILD_STAMP = true ? "3.7.4+a351e274" : "dev";
1989
2043
  var name = "dsh-sessions-manager";
1990
2044
  var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
1991
2045
  var MAX_TITLE = 80;
@@ -2052,7 +2106,7 @@ function requireSessionId(value) {
2052
2106
  }
2053
2107
  return value;
2054
2108
  }
2055
- function getActiveSessionId(context) {
2109
+ function legacyActiveSessionId(context) {
2056
2110
  try {
2057
2111
  const a = context.get("activeSession");
2058
2112
  if (a != null) return a && a.id != null ? a.id : typeof a === "string" ? a : null;
@@ -2070,6 +2124,12 @@ function getActiveSessionId(context) {
2070
2124
  }
2071
2125
  return null;
2072
2126
  }
2127
+ function getActiveSessionIds(context, tracker) {
2128
+ const reported = tracker ? tracker.activeIds() : null;
2129
+ if (reported && reported.size) return reported;
2130
+ const legacy = legacyActiveSessionId(context);
2131
+ return legacy != null ? /* @__PURE__ */ new Set([String(legacy)]) : /* @__PURE__ */ new Set();
2132
+ }
2073
2133
  function apply(ctx) {
2074
2134
  const w = ctx.workspaceRegistry;
2075
2135
  const sp = ctx.sessionPersistence;
@@ -2081,6 +2141,7 @@ function apply(ctx) {
2081
2141
  const metaCache = createSessionMetaCache();
2082
2142
  const titleIndex = createTitleIndexStore({ dir: TRASH_DIR, file: join12(TRASH_DIR, "title-index.json") });
2083
2143
  const emptyIndex = createEmptyScanStore({ dir: TRASH_DIR });
2144
+ const activeTracker = createActiveSessionTracker();
2084
2145
  const EMPTY_VERDICT_TTL_MS = 14 * 24 * 3600 * 1e3;
2085
2146
  async function hydrateFromPersist(ids, statsById) {
2086
2147
  const hits = /* @__PURE__ */ new Map();
@@ -2756,8 +2817,9 @@ function apply(ctx) {
2756
2817
  }
2757
2818
  async function moveOne(sid, targetPath) {
2758
2819
  requireCapability(capabilities, "move");
2759
- const activeId = getActiveSessionId(ctx);
2760
- if (activeId != null && String(activeId) === String(sid)) {
2820
+ const activeIds = getActiveSessionIds(ctx, activeTracker);
2821
+ const activeId = activeIds.values().next().value != null ? activeIds.values().next().value : null;
2822
+ if (activeIds.has(String(sid)) || activeId != null && String(activeId) === String(sid)) {
2761
2823
  const error = new Error("\u4F1A\u8BDD\u6B63\u88AB DSH \u6253\u5F00\uFF0C\u6682\u65F6\u65E0\u6CD5\u79FB\u52A8\uFF1B\u8BF7\u91CD\u542F DSH \u540E\u518D\u8BD5\u3002");
2762
2824
  error.status = 409;
2763
2825
  error.code = "DSM_SESSION_BUSY";
@@ -3300,7 +3362,7 @@ function apply(ctx) {
3300
3362
  const candidates = pickInactiveCandidates(items, {
3301
3363
  inactiveDays: days,
3302
3364
  skipStarred: store.settings.skipStarred,
3303
- activeSessionId: getActiveSessionId(ctx),
3365
+ activeSessionIds: [...getActiveSessionIds(ctx, activeTracker)],
3304
3366
  now
3305
3367
  });
3306
3368
  let archived = 0;
@@ -3636,6 +3698,20 @@ function apply(ctx) {
3636
3698
  path: "/archived-sessions/capabilities",
3637
3699
  handler: async (req, res) => json(res, { ...capabilities, buildStamp: BUILD_STAMP })
3638
3700
  }));
3701
+ disposers.push(ctx.webServer.register({
3702
+ kind: "exact",
3703
+ path: "/archived-sessions/active-session",
3704
+ handler: async (req, res) => {
3705
+ const body = await readJsonBody(req);
3706
+ const clientId = body && typeof body.clientId === "string" ? body.clientId : "default";
3707
+ const sid = body && typeof body.sessionId === "string" && body.sessionId ? body.sessionId : null;
3708
+ const accepted = activeTracker.report(clientId, sid);
3709
+ json(res, {
3710
+ ok: accepted,
3711
+ activeSessionIds: [...activeTracker.activeIds()]
3712
+ });
3713
+ }
3714
+ }));
3639
3715
  disposers.push(ctx.webServer.register({
3640
3716
  kind: "exact",
3641
3717
  path: "/archived-sessions/list",