dsh-sessions-manager 3.7.6 → 3.7.8

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
@@ -2019,13 +2019,61 @@ var LIFECYCLE_EVENT_TYPES = /* @__PURE__ */ new Set([
2019
2019
  "sandbox/mode",
2020
2020
  "approval/policy"
2021
2021
  ]);
2022
- function isEmptyEventTypes(types) {
2023
- if (!Array.isArray(types)) return false;
2024
- for (const t of types) {
2025
- if (typeof t !== "string" || !LIFECYCLE_EVENT_TYPES.has(t)) return false;
2022
+ function isInjectedMessage(msg) {
2023
+ const src = msg && typeof msg === "object" ? msg.source : null;
2024
+ if (!src || typeof src !== "object") return false;
2025
+ if (src.form === "instructions") return true;
2026
+ return typeof src.kind === "string" && src.kind.startsWith("plugin:");
2027
+ }
2028
+ function isNoiseEvent(event) {
2029
+ const type = typeof event === "string" ? event : event && typeof event === "object" ? event.type : null;
2030
+ if (typeof type !== "string") return false;
2031
+ if (LIFECYCLE_EVENT_TYPES.has(type)) return true;
2032
+ if (type !== "agent/inbox/spliced") return false;
2033
+ const data = event && typeof event === "object" ? event.data : null;
2034
+ if (!data || typeof data !== "object") return false;
2035
+ const removed = Number(data.removedCount);
2036
+ if (Number.isFinite(removed) && removed > 0) return false;
2037
+ if (!Array.isArray(data.inserted)) return false;
2038
+ if (data.inserted.length === 0) return true;
2039
+ return data.inserted.every(isInjectedMessage);
2040
+ }
2041
+ function isEmptyEventTypes(events) {
2042
+ if (!Array.isArray(events)) return false;
2043
+ for (const ev of events) {
2044
+ if (!isNoiseEvent(ev)) return false;
2026
2045
  }
2027
2046
  return true;
2028
2047
  }
2048
+ var SUBAGENT_CASCADE_LIMIT = 500;
2049
+ function subagentDescendantsOf(headers, rootId, limit = SUBAGENT_CASCADE_LIMIT) {
2050
+ const root = rootId == null ? "" : String(rootId);
2051
+ if (!root) return [];
2052
+ const kidsOf = /* @__PURE__ */ new Map();
2053
+ for (const h of Array.isArray(headers) ? headers : []) {
2054
+ if (!h || typeof h !== "object" || h.id == null || h.origin !== "subagent") continue;
2055
+ const id = String(h.id);
2056
+ const parent = h.parentSession != null ? String(h.parentSession) : "";
2057
+ if (!parent || parent === id) continue;
2058
+ if (!kidsOf.has(parent)) kidsOf.set(parent, []);
2059
+ kidsOf.get(parent).push(id);
2060
+ }
2061
+ const max = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : SUBAGENT_CASCADE_LIMIT;
2062
+ const out = [];
2063
+ const visited = /* @__PURE__ */ new Set([root]);
2064
+ const queue = [root];
2065
+ while (queue.length && out.length < max) {
2066
+ const cur = queue.shift();
2067
+ for (const kid of kidsOf.get(cur) || []) {
2068
+ if (visited.has(kid)) continue;
2069
+ visited.add(kid);
2070
+ out.push(kid);
2071
+ queue.push(kid);
2072
+ if (out.length >= max) break;
2073
+ }
2074
+ }
2075
+ return out;
2076
+ }
2029
2077
  function classifyLineage(header) {
2030
2078
  if (!header || typeof header !== "object") return null;
2031
2079
  const origin = header.origin === "subagent" ? "subagent" : null;
@@ -2039,7 +2087,7 @@ function emptyScanCandidate(sizeBytes) {
2039
2087
  }
2040
2088
 
2041
2089
  // src/index.js
2042
- var BUILD_STAMP = true ? "3.7.6+68c2ab69" : "dev";
2090
+ var BUILD_STAMP = true ? "3.7.8+846b91ab" : "dev";
2043
2091
  var name = "dsh-sessions-manager";
2044
2092
  var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
2045
2093
  var MAX_TITLE = 80;
@@ -2523,7 +2571,7 @@ function apply(ctx) {
2523
2571
  } catch (e) {
2524
2572
  }
2525
2573
  }
2526
- async function deleteOne(sid) {
2574
+ async function deleteOne(sid, opts = {}) {
2527
2575
  requireSessionId(sid);
2528
2576
  let header = null;
2529
2577
  let cwd = null;
@@ -2531,7 +2579,7 @@ function apply(ctx) {
2531
2579
  let removedPath = null;
2532
2580
  let persistenceEntry = null;
2533
2581
  try {
2534
- const entries = await persistence.listEntries();
2582
+ const entries = Array.isArray(opts.entries) ? opts.entries : await persistence.listEntries();
2535
2583
  const found = entries.find((entry) => entry.id === sid) || null;
2536
2584
  persistenceEntry = found;
2537
2585
  header = found ? found.header : null;
@@ -2576,6 +2624,7 @@ function apply(ctx) {
2576
2624
  wasArchived: archived,
2577
2625
  deletedAt: Date.now()
2578
2626
  };
2627
+ if (opts.cascadeFrom) entry.cascadeFrom = String(opts.cascadeFrom);
2579
2628
  const at = store.items.findIndex((t) => String(t.sessionId) === sid);
2580
2629
  if (at >= 0) store.items[at] = entry;
2581
2630
  else store.items.push(entry);
@@ -2583,6 +2632,64 @@ function apply(ctx) {
2583
2632
  });
2584
2633
  return { ok: true, trashed: true };
2585
2634
  }
2635
+ function subagentDescendantIdsFrom(entries, rootId) {
2636
+ const headers = [];
2637
+ for (const entry of Array.isArray(entries) ? entries : []) {
2638
+ if (entry && entry.header) headers.push(entry.header);
2639
+ }
2640
+ return subagentDescendantsOf(headers, rootId);
2641
+ }
2642
+ async function deleteWithSubagents(sid, opts = {}) {
2643
+ let entries = Array.isArray(opts.entries) ? opts.entries : null;
2644
+ if (!entries) {
2645
+ try {
2646
+ entries = await persistence.listEntries();
2647
+ } catch (e) {
2648
+ entries = null;
2649
+ }
2650
+ }
2651
+ const out = await deleteOne(sid, { entries });
2652
+ const cascade = opts.cascade !== false;
2653
+ if (!cascade || !entries) return { ...out, cascaded: 0, cascadedIds: [] };
2654
+ const kids = subagentDescendantIdsFrom(entries, sid);
2655
+ let alreadyTrashed = null;
2656
+ try {
2657
+ const store = await readTrashStore();
2658
+ alreadyTrashed = new Set(store.items.map((t) => String(t.sessionId)));
2659
+ } catch (e) {
2660
+ alreadyTrashed = null;
2661
+ }
2662
+ const cascadedIds = [];
2663
+ for (const kid of kids) {
2664
+ if (alreadyTrashed && alreadyTrashed.has(String(kid))) continue;
2665
+ try {
2666
+ await deleteOne(kid, { entries, cascadeFrom: sid });
2667
+ metaCache.invalidate(kid);
2668
+ cascadedIds.push(kid);
2669
+ } catch (e) {
2670
+ }
2671
+ }
2672
+ return { ...out, cascaded: cascadedIds.length, cascadedIds };
2673
+ }
2674
+ async function cascadeTrashChildren(sid, op) {
2675
+ let kids = [];
2676
+ try {
2677
+ const store = await readTrashStore();
2678
+ kids = store.items.filter((t) => String(t.cascadeFrom || "") === String(sid)).map((t) => String(t.sessionId));
2679
+ } catch (e) {
2680
+ return [];
2681
+ }
2682
+ const results = [];
2683
+ for (const kid of kids) {
2684
+ try {
2685
+ results.push({ sessionId: kid, ok: true, ...await op(kid) });
2686
+ metaCache.invalidate(kid);
2687
+ } catch (e) {
2688
+ results.push({ sessionId: kid, ok: false, error: String(e && e.message || e) });
2689
+ }
2690
+ }
2691
+ return results;
2692
+ }
2586
2693
  async function verifyTrashRestore(sid) {
2587
2694
  const sessions = ctx.get("sessions");
2588
2695
  if (sessions && sessions.get && sessions.get(sid)) {
@@ -3496,6 +3603,18 @@ function apply(ctx) {
3496
3603
  const payload = {
3497
3604
  titles: Object.fromEntries(authorityTitleCache),
3498
3605
  trashedSessionIds: store.items.map((item) => String(item.sessionId)),
3606
+ // issue #16:回收站按 cwd 的条数。折叠分组下上游把 group.sessions 收回成
3607
+ // 空数组,client 拿不到成员 id,只能用「组内会话总数 − 本组回收站条数」
3608
+ // 还原展开按钮该显示的数字;有了这份统计,那次扣减才是准确的而非估算。
3609
+ trashedCountByCwd: (() => {
3610
+ const acc = {};
3611
+ for (const item of store.items) {
3612
+ const cwd = typeof item.cwd === "string" ? item.cwd : "";
3613
+ if (!cwd) continue;
3614
+ acc[cwd] = (acc[cwd] || 0) + 1;
3615
+ }
3616
+ return acc;
3617
+ })(),
3499
3618
  purgedSessionIds: activeTombstones,
3500
3619
  lineage,
3501
3620
  // v3.6.2:还有预热/退避重试在途 → client 缩短轮询节拍,预热一完成就把
@@ -3549,11 +3668,29 @@ function apply(ctx) {
3549
3668
  const persistBatch = {};
3550
3669
  for (const [id, desc] of batch) {
3551
3670
  try {
3552
- const types = [];
3671
+ const events = [];
3553
3672
  await persistence.inspectSession(id, { onEvents: (b) => {
3554
- for (const ev of b || []) if (types.length < 64) types.push(ev && ev.type);
3673
+ for (const ev of b || []) {
3674
+ if (events.length >= 64) break;
3675
+ if (!ev) {
3676
+ events.push(void 0);
3677
+ continue;
3678
+ }
3679
+ if (ev.type !== "agent/inbox/spliced") {
3680
+ events.push({ type: ev.type });
3681
+ continue;
3682
+ }
3683
+ const data = ev.data && typeof ev.data === "object" ? ev.data : null;
3684
+ events.push({
3685
+ type: ev.type,
3686
+ data: data ? {
3687
+ removedCount: data.removedCount,
3688
+ inserted: Array.isArray(data.inserted) ? data.inserted.map((m) => ({ source: m && typeof m === "object" ? m.source : null })) : null
3689
+ } : null
3690
+ });
3691
+ }
3555
3692
  } });
3556
- const isEmpty = isEmptyEventTypes(types);
3693
+ const isEmpty = isEmptyEventTypes(events);
3557
3694
  emptyScanCache.set(id, { sizeBytes: desc.sizeBytes, empty: isEmpty });
3558
3695
  persistBatch[id] = { empty: isEmpty ? 1 : 0, fingerprint: `sz:${desc.sizeBytes}`, updatedAt: Date.now() };
3559
3696
  } catch (e) {
@@ -3826,7 +3963,7 @@ function apply(ctx) {
3826
3963
  const body = await readJsonBody(req);
3827
3964
  const sid = body && typeof body.sessionId === "string" ? body.sessionId : null;
3828
3965
  if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
3829
- const out = await deleteOne(sid);
3966
+ const out = await deleteWithSubagents(sid, { cascade: body.cascadeSubagents !== false });
3830
3967
  metaCache.invalidate(sid);
3831
3968
  json(res, out);
3832
3969
  } catch (e) {
@@ -3843,15 +3980,30 @@ function apply(ctx) {
3843
3980
  const ids = parseIds(body);
3844
3981
  if (!ids || ids.length === 0) return json(res, { ok: false, error: "missing sessionIds" }, 400);
3845
3982
  const results = [];
3983
+ let cascadeEntries = null;
3984
+ try {
3985
+ cascadeEntries = await persistence.listEntries();
3986
+ } catch (e) {
3987
+ cascadeEntries = null;
3988
+ }
3989
+ const cascade = body.cascadeSubagents !== false;
3990
+ const done = /* @__PURE__ */ new Set();
3846
3991
  for (const sid of ids) {
3847
3992
  try {
3848
- results.push({ sessionId: sid, ok: true, ...await deleteOne(sid) });
3993
+ if (done.has(sid)) {
3994
+ results.push({ sessionId: sid, ok: true, trashed: true, cascaded: 0 });
3995
+ continue;
3996
+ }
3997
+ const out = cascade && cascadeEntries ? await deleteWithSubagents(sid, { cascade: true, entries: cascadeEntries }) : await deleteOne(sid, { entries: cascadeEntries });
3998
+ done.add(sid);
3999
+ if (Array.isArray(out.cascadedIds)) for (const k of out.cascadedIds) done.add(k);
4000
+ results.push({ sessionId: sid, ok: true, ...out });
3849
4001
  metaCache.invalidate(sid);
3850
4002
  } catch (e) {
3851
4003
  results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
3852
4004
  }
3853
4005
  }
3854
- json(res, { ok: true, deleted: results.filter((r) => r.ok).length, results });
4006
+ json(res, { ok: true, deleted: results.filter((r) => r.ok).length, cascaded: results.reduce((n, r) => n + (Number(r.cascaded) || 0), 0), results });
3855
4007
  } catch (e) {
3856
4008
  json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
3857
4009
  }
@@ -3920,7 +4072,8 @@ function apply(ctx) {
3920
4072
  if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
3921
4073
  const out = await restoreFromTrash(sid);
3922
4074
  metaCache.invalidate(sid);
3923
- json(res, out);
4075
+ const cascaded = await cascadeTrashChildren(sid, (kid) => restoreFromTrash(kid));
4076
+ json(res, { ...out, cascaded: cascaded.filter((r) => r.ok).length, cascadedResults: cascaded.length ? cascaded : void 0 });
3924
4077
  } catch (e) {
3925
4078
  json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
3926
4079
  }
@@ -3944,7 +4097,19 @@ function apply(ctx) {
3944
4097
  await emptyIndex.remove([sid]);
3945
4098
  } catch (e) {
3946
4099
  }
3947
- json(res, out);
4100
+ const cascaded = await cascadeTrashChildren(sid, async (kid) => {
4101
+ const r = await purgeFromTrash(kid);
4102
+ try {
4103
+ await titleIndex.remove([kid]);
4104
+ } catch (e) {
4105
+ }
4106
+ try {
4107
+ await emptyIndex.remove([kid]);
4108
+ } catch (e) {
4109
+ }
4110
+ return r;
4111
+ });
4112
+ json(res, { ...out, cascaded: cascaded.filter((r) => r.ok).length });
3948
4113
  } catch (e) {
3949
4114
  json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
3950
4115
  }
@@ -3967,6 +4132,18 @@ function apply(ctx) {
3967
4132
  });
3968
4133
  await emptyIndex.remove([sid]).catch(() => {
3969
4134
  });
4135
+ await cascadeTrashChildren(sid, async (kid) => {
4136
+ const r = await purgeFromTrash(kid);
4137
+ try {
4138
+ await titleIndex.remove([kid]);
4139
+ } catch (e) {
4140
+ }
4141
+ try {
4142
+ await emptyIndex.remove([kid]);
4143
+ } catch (e) {
4144
+ }
4145
+ return r;
4146
+ });
3970
4147
  } catch (e) {
3971
4148
  results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
3972
4149
  }