dsh-sessions-manager 3.5.2 → 3.5.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-sessions-manager",
3
3
  "description": "DSH 设置面板会话管理器:归档 / 恢复 / 彻底删除 / 移动到其他工作区,带工作区标签与会话日期;统一「会话管理」面板。Session manager for the DeepSeek Harness settings panel — archive / restore / permanently delete / move sessions across workspaces, with workspace tags & session dates in one unified panel.",
4
- "version": "3.5.2",
4
+ "version": "3.5.3",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -1353,9 +1353,15 @@ function installSidebarSessionMenuAug() {
1353
1353
  sub.setAttribute('data-dsm-sub', '')
1354
1354
  sub.innerHTML = '<div class="dsm-sub-loading">加载工作区…</div>'
1355
1355
  const r = moveBtn.getBoundingClientRect()
1356
- sub.style.top = Math.max(8, Math.round(r.top - 4)) + 'px'
1357
- sub.style.left = Math.round(r.right + 10) + 'px'
1356
+ // Fixed-position card: once the async content renders it can be tall or
1357
+ // wide enough to hang past the viewport edges, so re-clamp both anchors
1358
+ // against the rendered size.
1359
+ const placeSub = () => {
1360
+ sub.style.top = Math.max(8, Math.min(Math.round(r.top - 4), window.innerHeight - sub.offsetHeight - 8)) + 'px'
1361
+ sub.style.left = Math.max(8, Math.min(Math.round(r.right + 10), window.innerWidth - sub.offsetWidth - 8)) + 'px'
1362
+ }
1358
1363
  document.body.appendChild(sub)
1364
+ placeSub()
1359
1365
  const closeSub = () => {
1360
1366
  activeSubClose = null
1361
1367
  if (sub.parentNode) sub.remove()
@@ -1384,7 +1390,7 @@ function installSidebarSessionMenuAug() {
1384
1390
  const items = ws.items || []
1385
1391
  const cur = (sess.items || []).find((x) => x.sessionId === info.id)
1386
1392
  const curPath = cur ? cur.workspacePath : (info.cwd || null)
1387
- if (!items.length) { sub.innerHTML = '<div class="dsm-sub-empty">(暂无可用工作区)</div>'; return }
1393
+ if (!items.length) { sub.innerHTML = '<div class="dsm-sub-empty">(暂无可用工作区)</div>'; placeSub(); return }
1388
1394
  sub.innerHTML = ''
1389
1395
  items.forEach((w) => {
1390
1396
  const isCur = !!curPath && w.path === curPath
@@ -1414,8 +1420,10 @@ function installSidebarSessionMenuAug() {
1414
1420
  })
1415
1421
  sub.appendChild(b)
1416
1422
  })
1423
+ placeSub()
1417
1424
  }).catch((e) => {
1418
1425
  sub.innerHTML = '<div class="dsm-sub-err">' + escapeHtml(String((e && e.message) || e)) + '</div>'
1426
+ placeSub()
1419
1427
  })
1420
1428
  }
1421
1429
  const openDeleteConfirm = (id) => {
@@ -1510,6 +1518,11 @@ function installSidebarSessionMenuAug() {
1510
1518
  viewport.appendChild(del.wrap)
1511
1519
  // 标记未读 goes to the very top of the menu (above DSH's native items).
1512
1520
  viewport.insertBefore(mark.wrap, viewport.firstElementChild)
1521
+ // DSH's portalled Menu clamped its position using the height it measured
1522
+ // before this shim injected rows, so the grown card can hang past the
1523
+ // viewport bottom and cover the sidebar foot. Reuse the host's own
1524
+ // placement by firing the resize event it listens for while open.
1525
+ window.dispatchEvent(new Event('resize'))
1513
1526
  }
1514
1527
 
1515
1528
  const seen = new WeakSet()
@@ -13,6 +13,8 @@
13
13
  // is an opaque change token valid ONLY within one service instance and one
14
14
  // session id (see src/session-meta-cache.js).
15
15
 
16
+ import { locateSessionArtifacts } from '../handle-era-paths.js'
17
+
16
18
  const DEFAULT_CHUNK = 400
17
19
  // 防御上限:一次 inspect 的分块循环绝不能无限自旋(后端 read 行为异常时快速失败)。
18
20
  const MAX_CHUNKS = 20000
package/src/index.js CHANGED
@@ -625,12 +625,31 @@ export function apply(ctx) {
625
625
  const entry = store.items.find((t) => String(t.sessionId) === sid)
626
626
  if (!entry) { const error = new Error('回收站中找不到该会话'); error.status = 404; throw error }
627
627
  let target = null
628
+ let locatedHeader = null
628
629
  try {
629
630
  const entries = await persistence.listEntries()
630
631
  const current = entries.find((entry) => entry.id === sid)
631
- const located = current && persistence.locate(current.header)
632
+ locatedHeader = current ? current.header : null
633
+ // locateVerified:legacy 走官方 locate;handle 时代官方收走了 locate,
634
+ // 改由三层守卫推导(root → 目录结构 → id 归属),失败返回 null。
635
+ // 比仅靠回收站条目里的 originalPath 更可靠:originalPath 过期或缺失时
636
+ // 仍能从当前存储布局重新推导。
637
+ const located = current ? await persistence.locateVerified(current.header) : null
632
638
  if (located && typeof located.path === 'string') target = located.path
633
639
  } catch (e) {}
640
+ // 幽灵记录兜底:会话不在 list 里(后端索引滞后/索引缺失)但日志仍在盘上。
641
+ // 用 statSession 拿官方 header,再走守卫推导,避免退化成「只删单文件」
642
+ // 或直接 409 拒绝。
643
+ if (!locatedHeader && typeof persistence.statSession === 'function') {
644
+ try {
645
+ const snap = await persistence.statSession(sid)
646
+ if (snap && snap.header) {
647
+ locatedHeader = snap.header
648
+ const located = await persistence.locateVerified(snap.header)
649
+ if (located && typeof located.path === 'string') target = located.path
650
+ }
651
+ } catch (e) {}
652
+ }
634
653
  if (!target && typeof entry.originalPath === 'string') target = entry.originalPath
635
654
  if (!target) {
636
655
  const error = new Error('无法确认该会话的物理日志位置,已停止永久删除')