dsh-sessions-manager 3.5.1 → 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/README.en.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  > DSH session manager: archive, move, restore, and inspect sessions from **Settings → 会话管理**; mark unread, move, and delete sessions directly from the **main sidebar**. Deleted sessions go to the recycle bin first and can be restored or permanently purged.
14
14
 
15
- A persistent DSH plugin (host + browser halves) covering both the **settings panel** and the **main sidebar**, so frequent session actions don't require opening Settings. It understands both legacy header lists/`readFrom` and the new snapshot/`SessionHandle` API, enabling actions according to the current Runtime's verified capabilities. Unverified purge or migration paths are disabled in both UI and Host to prevent false success.
15
+ A persistent DSH plugin (host + browser halves) covering both the **settings panel** and the **main sidebar**, so frequent session actions don't require opening Settings. It understands both legacy header lists/`readFrom` and the new snapshot/`SessionHandle` API, enabling actions according to the current Runtime's verified capabilities. Purge and migration paths are enabled only when they can be verified — on newer runtimes they are backed by guarded path derivation and write-ownership probes; anything unverifiable is disabled in both UI and Host to prevent false success.
16
16
 
17
17
  ## Features
18
18
 
@@ -159,9 +159,19 @@ lib/client.js pre-built client (ModuleLoader CJS handshake)
159
159
  - Works in both DSH Desktop and DSH web (same host + client halves).
160
160
  - Peer dependencies are listed in `package.json`; `react` and `@deepseek-ai/*` are provided by the DSH runtime.
161
161
  - `0.1.2-rc.1`: the existing read, archive, recycle-bin, permanent-purge, and cross-workspace move paths remain available.
162
- - `0.1.3-alpha.1`: snapshot lists and read-only `SessionHandle` flows are supported. Permanent purge and cross-workspace move are disabled when no verified safe path is available; other management functions continue to work. The panel shows the effective capabilities.
162
+ - `0.1.3-alpha.1`: snapshot lists and chunked `SessionHandle` read flows are supported. Permanent purge and cross-workspace move are restored via guarded path derivation (see the behavior notes below); they degrade gracefully only when the storage root cannot be confirmed. The panel shows the effective capabilities.
163
163
  - Unverified future runtimes expose only capabilities the plugin can safely identify; method presence alone is not presented as behavioral compatibility.
164
164
 
165
+ ### Behavior differences and degradations under `0.1.3-alpha.1`
166
+
167
+ - **Recycle-bin restore is verified**: before restoring, the plugin confirms the underlying stored session still exists (live / `stat` / listing, in that order). It returns accurate errors when the session is gone (`DSM_SESSION_MISSING`), was permanently purged (`DSM_SESSION_PURGED`), or the index still lists it but the log file has vanished (`DSM_SESSION_LOG_MISSING`); when the log location cannot be verified the entry is honestly reported as `unverified` instead of silently passing. Restoring a session whose workspace was deleted succeeds and notes that it now lives under "未分组" (ungrouped).
168
+ - **Permanent purge / empty recycle bin / automatic physical cleanup**: the official public contract offers no delete API, so the plugin keeps the legacy-era semi-official approach — a **three-layer guarded path derivation** starting from the backend instance's storage-root field (root → session directory structure → session-id ownership check), followed by a whole-directory removal verified with the official `stat`. Sessions with an active writer are refused (409). When the derivation fails (e.g. the storage root is unavailable), the actions degrade to disabled with an explicit reason instead of deleting blindly.
169
+ - **Cross-workspace move**: the primary path replays events through the official `create` + `append` (writer-ownership probe refuses active sessions, before/after `revision` checks guard against concurrent writes, backup + rollback guarantees no half-moved state on failure); when the backend holds a ghost record for the same id it falls back to relocating the log with a frame0 cwd rewrite (frame count and content verified byte-for-byte). Paths come from the same guarded derivation; the workspace grouping index is rebuilt automatically after the move — no restart needed.
170
+ - **Purge tombstones never suppress new sessions**: if a session with the same id is recreated later, the tombstone yields automatically and the new session appears normally in lists and the sidebar.
171
+ - **Metadata-cache change tokens**: on the new runtime the plugin prefers the official `stat`/`list` snapshot `revision` (comparable only within one service instance and one session id) to detect changes; legacy runtimes keep the `(mtime, size)` file fingerprint. Revisions are never persisted into the cross-process title index. List metadata comes straight from `snapshot.header` and titles from the batch projection — a missing title does **not** trigger a full-log decode, avoiding amplification of the known historical-session loading regression in alpha.
172
+ - **Auto-archive**: the new runtime exposes no reliable "last activity" timestamp; the sweep skips (`no-activity-data`) when idleness cannot be proven, and never archives on a guess.
173
+ - **Sidebar injection**: DOM / React fiber recognition is centralized in a versioned adapter; when the upstream sidebar shape is no longer recognized, all injections degrade safely and the official sidebar is left untouched. While recognition works, updates are driven incrementally by a MutationObserver with only a low-frequency fallback check.
174
+
165
175
  ## License
166
176
 
167
177
  [MIT](./LICENSE) © TOBYCAI
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  > DSH 会话管理器:在**设置 → 会话管理**里统一归档、移动、恢复、查看详情;在**主页侧边栏**直接标记未读、移动、删除会话。删除先进入回收站,可恢复或彻底清理。
14
14
 
15
- 一个 DSH 持久化插件(host + browser 双半),同时覆盖「设置面板」与「主页侧边栏」两个入口,无需打开设置即可完成高频会话操作。插件同时识别旧式 header 列表/`readFrom` 与新版 snapshot/`SessionHandle`,并按当前 Runtime 的实际能力启用安全操作;尚未验证的删除或迁移路径会在界面与 Host 端同步禁用,避免假成功。
15
+ 一个 DSH 持久化插件(host + browser 双半),同时覆盖「设置面板」与「主页侧边栏」两个入口,无需打开设置即可完成高频会话操作。插件同时识别旧式 header 列表/`readFrom` 与新版 snapshot/`SessionHandle`,按当前 Runtime 的实际能力启用操作:删除与迁移路径必须可验证才会开放(新版 Runtime 上由守卫式路径推导与写所有权探测保障),无法验证时在界面与 Host 端同步禁用,避免假成功。
16
16
 
17
17
  ## 功能
18
18
 
@@ -23,7 +23,7 @@
23
23
  - **冷态标题同步**:侧栏使用日志中最新的 `session/title` 修正冷启动缓存,改名后的会话无需先打开即可显示新名称。
24
24
  - **侧栏跨工作区拖拽**:直接把会话拖到目标工作区标题即可切换工作区;目标高亮、同工作区拦截、失败反馈,并保留“更多 → 移动会话”作为键盘操作入口。
25
25
  - **归档 / 恢复**:归档把会话从侧栏隐藏;恢复取消归档并放回原工作区分组。
26
- - **移动到工作区**:任选**已有工作区**或**新建目录路径**(自动创建),新建目录支持点击 **「浏览…」** 调用系统目录选择窗口。会话的工作目录与日志一起迁移;即使会话处于打开状态也可安全移动。
26
+ - **移动到工作区**:任选**已有工作区**或**新建目录路径**(自动创建),新建目录支持点击 **「浏览…」** 调用系统目录选择窗口。会话的工作目录与日志一起迁移。旧版 Runtime(`0.1.2-rc.1`)下已打开的会话也可移动;新版 Runtime(`0.1.3-alpha.1`)为避免并发写入损坏日志,正在写入的会话会被拒绝移动,待其空闲后重试即可。
27
27
  - **会话详情**:展开单条会话查看**磁盘占用**、**轮次 / 步骤 / 用户·助手消息 / 工具调用 / 图片附件**统计、**工具使用分布**、**搜索·抓取记录**、**write/edit 写过的文件列表**(已过滤磁盘上已不存在的路径),以及**血统**(父会话 / 子会话 / 子代理)。
28
28
  - **导出**:详情面板底部提供两个入口——「**下载原始日志 (ZIP)**」直接走 DSH 官方 `session.export` 端点(含子会话与附件,持久化后端不支持时自动隐藏);「**导出 Markdown**」由本插件把会话渲染为人类可读对话记录(front matter + 按轮分节 + 用户 / 助手 / 工具调用摘要,流式增量不重复)。
29
29
  - **存储占用分析**:维护栏的「存储占用」按钮按需展开,按工作区聚合会话日志的磁盘占用(占比条 + 会话数),并列出占用最大的会话 Top 10。纯只读统计,不修改任何数据;默认收起、展开时才统计,因此不会拖慢会话列表的加载。
@@ -162,6 +162,16 @@ lib/client.js 预构建 client(ModuleLoader CJS handshake)
162
162
  - `0.1.3-alpha.1`:支持 snapshot 列表和 `SessionHandle` 只读流程;永久删除与跨工作区移动在缺少已验证安全路径时自动禁用,其余管理能力继续工作。能力以面板实际提示为准。
163
163
  - 未经验证的未来 Runtime 默认只开放能够识别的安全能力;插件不会用方法存在与否冒充行为兼容。
164
164
 
165
+ ### `0.1.3-alpha.1` 下的行为差异与降级说明
166
+
167
+ - **回收站恢复需要校验**:恢复前会确认底层会话仍存在(live / `stat` / 列表三级判定)。底层会话已不存在(`DSM_SESSION_MISSING`)、已被彻底删除(`DSM_SESSION_PURGED`)或索引仍在但日志文件消失(`DSM_SESSION_LOG_MISSING`)时返回准确错误;无法核验日志位置时如实标注 `unverified`,不会假装校验通过。恢复到「工作区已删除」的会话会成功并提示其暂归「未分组」。
168
+ - **彻底删除 / 清空回收站 / 自动物理清理**:官方公共契约未提供删除 API,插件沿用 legacy 时代的半官方路线——从后端实例的存储根目录字段出发做**三层守卫式路径推导**(根目录 → 会话目录结构 → 会话 ID 归属校验),通过后整目录删除并以官方 `stat` 复核;存在活跃写入的会话会被拒绝(409)。推导失败(如无法确认存储根目录)时自动禁用并说明原因,绝不盲删。
169
+ - **跨工作区移动**:主路径为官方 `create` + `append` 事件重放(写所有权探测拒绝活跃会话、revision 前后校验防并发写入、备份回滚保证失败不留半移动状态);后端存在同 ID 幽灵记录时回退到 frame0 cwd 改写搬运(帧数与内容逐位校验)。路径同样来自守卫式推导;移动后自动重建工作区分组索引,无需重启。
170
+ - **彻底删除墓碑不压制新会话**:若同 ID 会话被重新创建,墓碑自动让位,新会话正常出现在列表与侧栏。
171
+ - **元数据缓存的变更令牌**:新 Runtime 优先使用官方 `stat`/`list` 快照的 `revision`(仅同一 service 实例、同一会话内可比较)判断会话是否变化,旧 Runtime 继续使用 `(mtime, size)` 文件指纹;`revision` 绝不写入跨进程持久索引。列表元数据直接取自 `snapshot.header`,标题走批量投影——没有标题时**不会**自动解码整本日志,避免放大 alpha 已知的历史会话加载性能回退。
172
+ - **自动归档**:新 Runtime 不提供可靠的「最后活跃时间」,无法证明会话闲置时检查会跳过(`no-activity-data`),绝不基于猜测归档。
173
+ - **侧栏注入**:DOM / React fiber 识别收敛为可版本化的 adapter;上游侧栏结构不被识别时整体安全停用,不影响官方侧栏本身。识别正常时以 MutationObserver 增量驱动为主,仅保留低频兜底检查。
174
+
165
175
  ## License
166
176
 
167
177
  [MIT](./LICENSE) © TOBYCAI
package/lib/client.js CHANGED
@@ -44,6 +44,8 @@ function dotStateFor({ manualUnread = false, dataState = "", isActive = false }
44
44
  case "running":
45
45
  return "running";
46
46
  case "warning":
47
+ case "waiting":
48
+ case "needs-attention":
47
49
  return "feedback";
48
50
  case "error":
49
51
  return "error";
@@ -96,12 +98,12 @@ function loadPanelPrefs() {
96
98
  }
97
99
  }
98
100
  var CSS = `
99
- .archv{--dsm-radius-tag:4px;display:flex;flex-direction:column;gap:4px;max-width:800px;padding:8px 2px 28px}
101
+ .archv{--dsm-radius-tag:4px;--dsm-radius-ctl:9px;--dsm-radius-sheet:10px;--dsm-radius-card:12px;display:flex;flex-direction:column;gap:4px;max-width:800px;padding:8px 2px 28px}
100
102
  .archv-head{display:flex;align-items:center;gap:10px;margin:0 0 2px}
101
103
  .archv-title{font-size:16px;font-weight:650;color:var(--dsw-alias-label-primary);letter-spacing:-0.01em;margin:0}
102
104
  .archv-count{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-subtle);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:1px 8px;flex:none}
103
105
  .archv-sub{font-size:12px;line-height:1.55;color:var(--dsw-alias-label-tertiary);margin:0 0 12px;max-width:64ch}
104
- .archv-err{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);border-radius:10px;color:var(--dsw-alias-state-error-primary);font-size:12px;margin-bottom:10px}
106
+ .archv-err{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);border-radius:var(--dsm-radius-sheet);color:var(--dsw-alias-state-error-primary);font-size:12px;margin-bottom:10px}
105
107
  .archv-errretry{appearance:none;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent);background:transparent;color:inherit;border-radius:8px;padding:4px 10px;font-size:11px;cursor:pointer;flex:none}
106
108
  .sess-filter{display:flex;align-items:center;gap:6px;margin:0 0 12px}
107
109
  .sess-fbtn{appearance:none;min-height:30px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-fill-subtle);color:var(--dsw-alias-label-secondary);border-radius:999px;font-size:12px;font-weight:500;cursor:pointer}
@@ -110,13 +112,13 @@ var CSS = `
110
112
  .sess-tools{display:grid;grid-template-columns:minmax(180px,1fr) minmax(140px,.7fr) minmax(130px,.55fr);gap:8px;margin:0 0 8px}
111
113
  .sess-field{display:flex;flex-direction:column;gap:5px;min-width:0}
112
114
  .sess-field label{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary)}
113
- .sess-field input,.sess-field select{box-sizing:border-box;width:100%;min-height:36px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px}
115
+ .sess-field input,.sess-field select{box-sizing:border-box;width:100%;min-height:36px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-ctl);background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px}
114
116
  .sess-results{font-size:11px;color:var(--dsw-alias-label-tertiary);margin:0 0 4px}
115
117
  .archv button:focus-visible,.archv input:focus-visible,.archv select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
116
118
  .sess-batch{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 2px 4px;margin-bottom:4px}
117
119
  .sess-btntext{font-size:12px;color:var(--dsw-alias-label-tertiary);flex:none}
118
120
  .archv-list{display:flex;flex-direction:column;gap:8px}
119
- .archv-card{display:flex;flex-direction:column;align-items:stretch;gap:0;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-fill-elevated);transition:border-color .15s ease,background-color .15s ease}
121
+ .archv-card{display:flex;flex-direction:column;align-items:stretch;gap:0;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-card);background:var(--dsw-alias-fill-elevated);transition:border-color .15s ease,background-color .15s ease}
120
122
  .archv-card:hover{border-color:var(--dsw-alias-border-l4)}
121
123
  .archv-card-exp{border-color:var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-1)}
122
124
  .archv-row{display:flex;align-items:center;gap:14px;width:100%;min-width:0}
@@ -137,16 +139,16 @@ var CSS = `
137
139
  .archv-star-on svg{fill:currentColor}
138
140
  .archv-body{flex:1;min-width:0;display:flex;align-items:center;gap:12px}
139
141
  .archv-actions{display:flex;gap:8px;flex:none;flex-wrap:nowrap;justify-content:flex-end}
140
- .archv-btn{appearance:none;min-height:32px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-fill-subtle);color:var(--dsw-alias-label-secondary);border-radius:9px;font-size:12px;font-weight:500;cursor:pointer;white-space:nowrap;display:inline-flex;align-items:center;justify-content:center;gap:6px;text-align:center;transition:background-color .15s ease,border-color .15s ease,color .15s ease}
142
+ .archv-btn{appearance:none;min-height:32px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-fill-subtle);color:var(--dsw-alias-label-secondary);border-radius:var(--dsm-radius-ctl);font-size:12px;font-weight:500;cursor:pointer;white-space:nowrap;display:inline-flex;align-items:center;justify-content:center;gap:6px;text-align:center;transition:background-color .15s ease,border-color .15s ease,color .15s ease}
141
143
  .archv-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
142
144
  .archv-btn:disabled{opacity:.5;cursor:default}
143
145
  .archv-del{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent)}
144
146
  .archv-del:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
145
147
  .archv-go{color:var(--dsw-alias-state-business-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 45%,transparent)}
146
148
  .archv-go:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);color:var(--dsw-alias-state-business-primary)}
147
- .archv-empty{display:flex;align-items:center;gap:10px;padding:20px 14px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
149
+ .archv-empty{display:flex;align-items:center;gap:10px;padding:20px 14px;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsm-radius-card);color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
148
150
  .archv-skel{display:flex;flex-direction:column;gap:8px}
149
- .archv-skel-card{height:58px;border-radius:12px;background:var(--dsw-alias-fill-subtle);position:relative;overflow:hidden}
151
+ .archv-skel-card{height:58px;border-radius:var(--dsm-radius-card);background:var(--dsw-alias-fill-subtle);position:relative;overflow:hidden}
150
152
  .archv-skel-card::after{content:'';position:absolute;inset:0;transform:translateX(-100%);background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--dsw-alias-fill-elevated) 75%,transparent),transparent);animation:archv-shimmer 1.4s infinite}
151
153
  .archv-status{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:60;background:var(--dsw-alias-fill-elevated);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);padding:9px 16px;border-radius:999px;font-size:12px;box-shadow:0 8px 24px rgb(0 0 0/.18);display:flex;align-items:center;gap:8px;animation:archv-pop .18s ease-out;max-width:min(90vw,420px)}
152
154
  .archv-spin{width:12px;height:12px;border:2px solid color-mix(in srgb,var(--dsw-alias-label-secondary) 35%,transparent);border-top-color:var(--dsw-alias-label-secondary);border-radius:50%;animation:archv-rot .8s linear infinite;flex:none}
@@ -155,24 +157,24 @@ var CSS = `
155
157
  @keyframes archv-pop{from{opacity:0;transform:translateX(-50%) translateY(10px)}}
156
158
  @media (prefers-reduced-motion:reduce){.archv-skel-card::after{animation:none}.archv-card,.archv-btn{transition:none}.archv-status,.archv-spin{animation:none}}
157
159
  @media (max-width:640px){.archv-card{flex-direction:column;align-items:stretch;gap:10px}.archv-actions{justify-content:flex-end}.sess-tools{grid-template-columns:1fr}.sess-fbtn,.archv-btn{min-height:40px}}
158
- .mv-sheet{width:100%;box-sizing:border-box;display:flex;flex-direction:column;gap:12px;margin-top:12px;padding:14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-fill-subtle)}
160
+ .mv-sheet{width:100%;box-sizing:border-box;display:flex;flex-direction:column;gap:12px;margin-top:12px;padding:14px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-sheet);background:var(--dsw-alias-fill-subtle)}
159
161
  .mv-sheet-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
160
162
  .mv-sheet-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}
161
163
  .mv-sheet-close{appearance:none;width:26px;height:26px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;border-radius:7px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1}
162
164
  .mv-sheet-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
163
- .mv-seg{display:flex;gap:2px;padding:2px;background:var(--dsw-alias-fill-elevated);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;width:100%}
165
+ .mv-seg{display:flex;gap:2px;padding:2px;background:var(--dsw-alias-fill-elevated);border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-sheet);width:100%}
164
166
  .mv-segbtn{appearance:none;flex:1;min-height:30px;padding:0 12px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);border-radius:8px;font-size:12px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px}
165
167
  .mv-segbtn:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
166
168
  .mv-segbtn-on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgb(0 0 0/.08)}
167
169
  .mv-field{display:flex;flex-direction:column;gap:6px}
168
170
  .mv-field label.mv-field-label{font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary)}
169
- .mv-field select,.mv-field input[type=text]{box-sizing:border-box;appearance:none;width:100%;min-height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-primary);font-size:12px;font-family:inherit}
171
+ .mv-field select,.mv-field input[type=text]{box-sizing:border-box;appearance:none;width:100%;min-height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-ctl);background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-primary);font-size:12px;font-family:inherit}
170
172
  .mv-field select:focus-visible,.mv-field input[type=text]:focus-visible,.mv-sheet-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
171
173
  .mv-browse-row{display:flex;align-items:center;gap:8px}
172
174
  .mv-browse-row input[type=text]{flex:1;min-width:0}
173
175
  .mv-foot{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin-top:2px}
174
176
  @media (max-width:640px){.archv-row{flex-wrap:wrap}.mv-sheet{padding:12px}}
175
- .dtl-sheet{margin-top:12px;padding:14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-fill-subtle)}
177
+ .dtl-sheet{margin-top:12px;padding:14px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-sheet);background:var(--dsw-alias-fill-subtle)}
176
178
  .dtl-sheet-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
177
179
  .dtl-sheet-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}
178
180
  .dtl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:8px;margin-top:10px}
@@ -193,7 +195,7 @@ var CSS = `
193
195
  .more-wrap{position:relative;flex:none}
194
196
  .more-btn{appearance:none;width:28px;height:28px;border:none;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}
195
197
  .more-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
196
- .more-menu{position:absolute;top:calc(100% + 4px);right:0;z-index:60;min-width:160px;padding:5px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l3);border-radius:10px;box-shadow:0 10px 32px rgb(0 0 0/.24);display:flex;flex-direction:column;gap:1px}
198
+ .more-menu{position:absolute;top:calc(100% + 4px);right:0;z-index:60;min-width:160px;padding:5px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l3);border-radius:var(--dsm-radius-sheet);box-shadow:0 10px 32px rgb(0 0 0/.24);display:flex;flex-direction:column;gap:1px}
197
199
  .more-item{appearance:none;display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border:none;background:transparent;color:var(--dsw-alias-label-primary);border-radius:7px;font-size:12.5px;cursor:pointer;white-space:nowrap;text-align:left}
198
200
  .more-item:hover{background:var(--dsw-alias-interactive-bg-hover)}
199
201
  .more-item-danger{color:var(--dsw-alias-state-error-primary)}
@@ -436,9 +438,13 @@ function SessionPanel({ workspacesSvc }) {
436
438
  const w = workspaces.find((x) => x.workspaceId === id);
437
439
  return w ? w.path : "";
438
440
  };
439
- const actionCapability = (name) => capabilities && capabilities.actions && capabilities.actions[name] || { available: false, reason: "\u6B63\u5728\u68C0\u67E5\u5F53\u524D DSH \u7684\u517C\u5BB9\u80FD\u529B\u2026" };
440
- const canPurge = actionCapability("purge");
441
- const canMove = actionCapability("move");
441
+ const actionCapability = (name, legacy) => {
442
+ const source = capabilities && capabilities.actions;
443
+ return source && (source[name] || legacy && source[legacy]) || { available: false, reason: "\u6B63\u5728\u68C0\u67E5\u5F53\u524D DSH \u7684\u517C\u5BB9\u80FD\u529B\u2026" };
444
+ };
445
+ const canPurge = actionCapability("physicalPurge", "purge");
446
+ const canMove = actionCapability("relocateSession", "move");
447
+ const canRestoreTrash = actionCapability("restoreIndexedSession", "restoreTrash");
442
448
  const archivedList = sessions ? sessions.filter((x) => x.archived) : [];
443
449
  const activeList = sessions ? sessions.filter((x) => !x.archived) : [];
444
450
  const starredList = starredOf(sessions);
@@ -579,9 +585,9 @@ function SessionPanel({ workspacesSvc }) {
579
585
  const restoreTrash = (sid) => {
580
586
  if (trashBusy) return;
581
587
  setTrashBusy(sid);
582
- postJSON("/archived-sessions/trash/restore", { sessionId: sid }).then(() => {
588
+ postJSON("/archived-sessions/trash/restore", { sessionId: sid }).then((r) => {
583
589
  setTrashBusy(null);
584
- showToast("\u5DF2\u6062\u590D\u4F1A\u8BDD");
590
+ showToast(r && r.workspaceGone ? "\u5DF2\u6062\u590D\u4F1A\u8BDD\uFF08\u539F\u5DE5\u4F5C\u533A\u5DF2\u5220\u9664\uFF0C\u4F1A\u8BDD\u6682\u5F52\u300C\u672A\u5206\u7EC4\u300D\uFF09" : "\u5DF2\u6062\u590D\u4F1A\u8BDD");
585
591
  loadTrash();
586
592
  refresh();
587
593
  }).catch((e) => {
@@ -1217,10 +1223,16 @@ function SessionPanel({ workspacesSvc }) {
1217
1223
  ] }),
1218
1224
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-field", children: [
1219
1225
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u6C38\u4E45\u6E05\u7406" }),
1220
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: trashBusy !== null || !trash.length || !canPurge.available, title: !canPurge.available ? canPurge.reason : void 0, onClick: () => setPurgeTarget("__all"), children: "\u6E05\u7A7A\u56DE\u6536\u7AD9" })
1226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: trashBusy !== null || !trash.length || !canPurge.available, "aria-disabled": !canPurge.available, title: !canPurge.available ? canPurge.reason : "\u7269\u7406\u5220\u9664\u8FD9\u4E9B\u65E5\u5FD7\uFF0C\u91CA\u653E\u78C1\u76D8\u7A7A\u95F4", onClick: () => setPurgeTarget("__all"), children: "\u6E05\u7A7A\u56DE\u6536\u7AD9" })
1221
1227
  ] })
1222
1228
  ] }),
1223
- !canPurge.available && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-empty", role: "status", children: canPurge.reason }),
1229
+ !canPurge.available && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-empty", role: "status", children: [
1230
+ canPurge.reason,
1231
+ "\u3002\u56DE\u6536\u7AD9\u6761\u76EE\u4F1A\u4E00\u76F4\u4FDD\u7559\uFF0C\u53EF\u968F\u65F6\u6062\u590D\uFF1B\u5F53\u524D DSH \u7248\u672C\u4E0B\u79FB\u5165\u56DE\u6536\u7AD9",
1232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "\u4E0D\u4F1A\u91CA\u653E\u78C1\u76D8\u7A7A\u95F4" }),
1233
+ "\uFF0C\u4F1A\u8BDD\u65E5\u5FD7\u4ECD\u5B8C\u6574\u4FDD\u7559\u5728\u539F\u5DE5\u4F5C\u533A\u76EE\u5F55\u3002"
1234
+ ] }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtl-note", children: "\u56DE\u6536\u7AD9\u662F\u8F6F\u5220\u9664\uFF1A\u65E5\u5FD7\u4ECD\u7559\u5728\u539F\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF0C\u300C\u5F7B\u5E95\u5220\u9664\u300D\u624D\u662F\u771F\u6B63\u91CA\u653E\u78C1\u76D8\u7A7A\u95F4\u7684\u4E00\u6B65\uFF08\u5F53\u524D\u7248\u672C\u672A\u652F\u6301\u65F6\u4F1A\u4FDD\u6301\u7981\u7528\uFF09\u3002" }),
1224
1236
  trashCheck && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: trashCheck.missing ? "archv-err" : "archv-empty", role: "status", children: [
1225
1237
  "\u6821\u9A8C\u5B8C\u6210\uFF1A",
1226
1238
  trashCheck.healthy,
@@ -1234,7 +1246,7 @@ function SessionPanel({ workspacesSvc }) {
1234
1246
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-trash-name", title: t.sessionId, children: t.title || t.sessionId }),
1235
1247
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-trash-date", children: fmtDate(t.deletedAt) }),
1236
1248
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-trash-actions", children: [
1237
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: trashBusy !== null, onClick: () => restoreTrash(t.sessionId), children: "\u6062\u590D" }),
1249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: trashBusy !== null || !canRestoreTrash.available, title: !canRestoreTrash.available ? canRestoreTrash.reason : "\u6062\u590D\u5230\u5220\u9664\u524D\u7684\u4F4D\u7F6E\u4E0E\u5F52\u6863\u72B6\u6001", onClick: () => restoreTrash(t.sessionId), children: "\u6062\u590D" }),
1238
1250
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: trashBusy !== null || !canPurge.available, title: !canPurge.available ? canPurge.reason : void 0, onClick: () => setPurgeTarget(t.sessionId), children: "\u5F7B\u5E95\u5220\u9664" })
1239
1251
  ] })
1240
1252
  ] }, t.sessionId)) })
@@ -1245,7 +1257,7 @@ function SessionPanel({ workspacesSvc }) {
1245
1257
  purgeTarget === "__all" ? "\u5168\u90E8\u56DE\u6536\u7AD9\u4F1A\u8BDD" : "\u8FD9\u4E2A\u4F1A\u8BDD",
1246
1258
  "\uFF1F"
1247
1259
  ] }),
1248
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dlg-text", children: "\u6B64\u64CD\u4F5C\u4F1A\u7269\u7406\u5220\u9664\u65E5\u5FD7\uFF0C\u65E0\u6CD5\u6062\u590D\u3002\u5F52\u6863\u3001\u7B5B\u9009\u548C\u91CD\u65B0\u5B89\u88C5\u63D2\u4EF6\u90FD\u4E0D\u80FD\u627E\u56DE\u8FD9\u4E9B\u6570\u636E\u3002" }),
1260
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dlg-text", children: "\u6B64\u64CD\u4F5C\u4F1A\u7269\u7406\u5220\u9664\u65E5\u5FD7\u5E76\u91CA\u653E\u78C1\u76D8\u7A7A\u95F4\uFF0C\u65E0\u6CD5\u6062\u590D\u3002\u5F52\u6863\u3001\u7B5B\u9009\u548C\u91CD\u65B0\u5B89\u88C5\u63D2\u4EF6\u90FD\u4E0D\u80FD\u627E\u56DE\u8FD9\u4E9B\u6570\u636E\u3002" }),
1249
1261
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dlg-actions", children: [
1250
1262
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", onClick: () => setPurgeTarget(null), children: "\u53D6\u6D88" }),
1251
1263
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", onClick: () => {
@@ -1356,57 +1368,83 @@ async function dsmLoadCapabilities() {
1356
1368
  }
1357
1369
  return dsmCapabilities;
1358
1370
  }
1359
- function dsmActionCapability(name) {
1360
- return dsmCapabilities && dsmCapabilities.actions && dsmCapabilities.actions[name];
1371
+ function dsmActionCapability(name, legacy) {
1372
+ const source = dsmCapabilities && dsmCapabilities.actions;
1373
+ return source ? source[name] || legacy && source[legacy] || null : null;
1361
1374
  }
1362
1375
  async function dsmLoadTrashIds() {
1363
1376
  try {
1364
1377
  const r = await postJSON("/archived-sessions/sidebar-state", {});
1365
1378
  dsmTrashIds = new Set((r && r.trashedSessionIds || []).map(String));
1366
1379
  dsmServerPurgedIds = new Set((r && r.purgedSessionIds || []).map(String));
1380
+ for (const id of [...dsmPendingPurged]) {
1381
+ if (dsmServerPurgedIds.has(id)) dsmPendingPurged.delete(id);
1382
+ }
1367
1383
  dsmAuthoritativeTitles = new Map(Object.entries(r && r.titles || {}).map(([id, title]) => [String(id), String(title)]));
1368
1384
  if (dsmRepaintDots) dsmRepaintDots();
1369
1385
  } catch (e) {
1370
1386
  }
1371
1387
  return dsmTrashIds;
1372
1388
  }
1373
- var DSM_KEY_PURGED = "dsm-purged-v1";
1374
- var dsmPurgedIds = null;
1389
+ var dsmPendingPurged = /* @__PURE__ */ new Set();
1375
1390
  function dsmLoadPurged() {
1376
- if (!dsmPurgedIds) {
1377
- try {
1378
- dsmPurgedIds = new Set(JSON.parse(localStorage.getItem(DSM_KEY_PURGED) || "[]"));
1379
- } catch (e) {
1380
- dsmPurgedIds = /* @__PURE__ */ new Set();
1381
- }
1382
- }
1383
- dsmServerPurgedIds.forEach((id) => dsmPurgedIds.add(id));
1384
- return dsmPurgedIds;
1385
- }
1386
- function dsmSavePurged() {
1387
- try {
1388
- localStorage.setItem(DSM_KEY_PURGED, JSON.stringify([...dsmLoadPurged()]));
1389
- } catch (e) {
1390
- }
1391
+ return /* @__PURE__ */ new Set([...dsmServerPurgedIds, ...dsmPendingPurged]);
1391
1392
  }
1392
1393
  function dsmMarkPurged(ids) {
1393
- const s = dsmLoadPurged();
1394
- ids.forEach((id) => s.add(String(id)));
1395
- dsmSavePurged();
1394
+ ids.forEach((id) => dsmPendingPurged.add(String(id)));
1396
1395
  if (dsmRepaintDots) dsmRepaintDots();
1397
1396
  }
1397
+ var SIDEBAR_ADAPTER_VERSION = 3;
1398
+ var SIDEBAR_ADAPTER_MAX_MISSES = 200;
1399
+ function createSidebarAdapter() {
1400
+ let disabled = false;
1401
+ let misses = 0;
1402
+ const findFiber = (el) => {
1403
+ const k = Object.keys(el).find((kk) => kk.startsWith("__reactFiber") || kk.startsWith("__reactInternalInstance"));
1404
+ return k ? el[k] : null;
1405
+ };
1406
+ const recognize = (row) => {
1407
+ const out = { node: null, group: null };
1408
+ if (disabled || !row || typeof row !== "object") return out;
1409
+ let f = findFiber(row);
1410
+ let guard = 0;
1411
+ while (f && guard++ < 300) {
1412
+ const props = f.memoizedProps;
1413
+ if (props && props.node && typeof props.node === "object") out.node = props.node;
1414
+ if (props && props.group && typeof props.group === "object") out.group = props.group;
1415
+ f = f.return;
1416
+ }
1417
+ if (!out.node && !out.group) {
1418
+ if (++misses >= SIDEBAR_ADAPTER_MAX_MISSES) {
1419
+ disabled = true;
1420
+ try {
1421
+ console.warn("[dsh-sessions-manager] \u4FA7\u680F\u6CE8\u5165 adapter v" + SIDEBAR_ADAPTER_VERSION + " \u5DF2\u505C\u7528\uFF1A\u672A\u8BC6\u522B\u5230\u5DF2\u77E5\u7684 DSH \u4FA7\u680F\u8282\u70B9\u7ED3\u6784\uFF08\u4E0A\u6E38\u53EF\u80FD\u5DF2\u6539\u7248\uFF09");
1422
+ } catch (e) {
1423
+ }
1424
+ }
1425
+ } else if (misses > 0) {
1426
+ misses = 0;
1427
+ }
1428
+ return out;
1429
+ };
1430
+ return {
1431
+ version: SIDEBAR_ADAPTER_VERSION,
1432
+ get disabled() {
1433
+ return disabled;
1434
+ },
1435
+ findFiber,
1436
+ recognize
1437
+ };
1438
+ }
1439
+ var sidebarAdapter = createSidebarAdapter();
1398
1440
  function installSidebarSessionMenuAug() {
1399
1441
  if (typeof document === "undefined") return;
1400
1442
  const AUG = "data-dsm-aug";
1401
1443
  let styleInjected = false;
1402
1444
  let activeSubClose = null;
1403
1445
  let hoverTimer = null;
1404
- const findFiber = (el) => {
1405
- const k = Object.keys(el).find((kk) => kk.startsWith("__reactFiber") || kk.startsWith("__reactInternalInstance"));
1406
- return k ? el[k] : null;
1407
- };
1408
1446
  const sessionInfoFromMenu = (menuEl) => {
1409
- let f = findFiber(menuEl);
1447
+ let f = sidebarAdapter.findFiber(menuEl);
1410
1448
  let guard = 0;
1411
1449
  while (f && guard++ < 300) {
1412
1450
  const p = f.memoizedProps;
@@ -1463,9 +1501,12 @@ function installSidebarSessionMenuAug() {
1463
1501
  sub.setAttribute("data-dsm-sub", "");
1464
1502
  sub.innerHTML = '<div class="dsm-sub-loading">\u52A0\u8F7D\u5DE5\u4F5C\u533A\u2026</div>';
1465
1503
  const r = moveBtn.getBoundingClientRect();
1466
- sub.style.top = Math.max(8, Math.round(r.top - 4)) + "px";
1467
- sub.style.left = Math.round(r.right + 10) + "px";
1504
+ const placeSub = () => {
1505
+ sub.style.top = Math.max(8, Math.min(Math.round(r.top - 4), window.innerHeight - sub.offsetHeight - 8)) + "px";
1506
+ sub.style.left = Math.max(8, Math.min(Math.round(r.right + 10), window.innerWidth - sub.offsetWidth - 8)) + "px";
1507
+ };
1468
1508
  document.body.appendChild(sub);
1509
+ placeSub();
1469
1510
  const closeSub = () => {
1470
1511
  activeSubClose = null;
1471
1512
  if (sub.parentNode) sub.remove();
@@ -1498,6 +1539,7 @@ function installSidebarSessionMenuAug() {
1498
1539
  const curPath = cur ? cur.workspacePath : info.cwd || null;
1499
1540
  if (!items.length) {
1500
1541
  sub.innerHTML = '<div class="dsm-sub-empty">\uFF08\u6682\u65E0\u53EF\u7528\u5DE5\u4F5C\u533A\uFF09</div>';
1542
+ placeSub();
1501
1543
  return;
1502
1544
  }
1503
1545
  sub.innerHTML = "";
@@ -1527,8 +1569,10 @@ function installSidebarSessionMenuAug() {
1527
1569
  });
1528
1570
  sub.appendChild(b);
1529
1571
  });
1572
+ placeSub();
1530
1573
  }).catch((e) => {
1531
1574
  sub.innerHTML = '<div class="dsm-sub-err">' + escapeHtml(String(e && e.message || e)) + "</div>";
1575
+ placeSub();
1532
1576
  });
1533
1577
  };
1534
1578
  const openDeleteConfirm = (id) => {
@@ -1589,7 +1633,7 @@ function installSidebarSessionMenuAug() {
1589
1633
  const move = mk("\u79FB\u52A8\u4F1A\u8BDD", ICON_MOVE, false);
1590
1634
  const del = mk("\u5220\u9664\u4F1A\u8BDD", ICON_DEL, true);
1591
1635
  const mark = mk(dsmLoadManual().has(info.id) ? "\u6807\u8BB0\u5DF2\u8BFB" : "\u6807\u8BB0\u672A\u8BFB", ICON_UNREAD, false);
1592
- const moveCapability = dsmActionCapability("move");
1636
+ const moveCapability = dsmActionCapability("relocateSession") || dsmActionCapability("move");
1593
1637
  if (!moveCapability || !moveCapability.available) {
1594
1638
  move.btn.disabled = true;
1595
1639
  move.btn.title = moveCapability && moveCapability.reason || "\u6B63\u5728\u68C0\u67E5\u5F53\u524D\u7248\u672C\u7684\u79FB\u52A8\u80FD\u529B";
@@ -1625,9 +1669,11 @@ function installSidebarSessionMenuAug() {
1625
1669
  viewport.appendChild(move.wrap);
1626
1670
  viewport.appendChild(del.wrap);
1627
1671
  viewport.insertBefore(mark.wrap, viewport.firstElementChild);
1672
+ window.dispatchEvent(new Event("resize"));
1628
1673
  };
1629
1674
  const seen = /* @__PURE__ */ new WeakSet();
1630
1675
  const obs = new MutationObserver(() => {
1676
+ if (sidebarAdapter.disabled) return;
1631
1677
  const menus = document.querySelectorAll('body > [role="menu"]');
1632
1678
  menus.forEach((menuEl) => {
1633
1679
  if (seen.has(menuEl)) return;
@@ -1651,21 +1697,7 @@ function installSidebarStatusDots() {
1651
1697
  s.textContent = SIDEBAR_AUG_CSS;
1652
1698
  document.head.appendChild(s);
1653
1699
  }
1654
- const findFiber = (el) => {
1655
- const k = Object.keys(el).find((kk) => kk.startsWith("__reactFiber") || kk.startsWith("__reactInternalInstance"));
1656
- return k ? el[k] : null;
1657
- };
1658
- const fiberProp = (el, pred) => {
1659
- let f = findFiber(el);
1660
- let g = 0;
1661
- while (f && g++ < 300) {
1662
- const p = f.memoizedProps;
1663
- if (p && pred(p)) return pred(p);
1664
- f = f.return;
1665
- }
1666
- return null;
1667
- };
1668
- const rowNode = (row) => fiberProp(row, (p) => p.node && typeof p.node.id === "string" && p.node.id ? p.node : null);
1700
+ const rowNode = (row) => sidebarAdapter.recognize(row).node;
1669
1701
  const rowId = (row) => {
1670
1702
  const node = rowNode(row);
1671
1703
  return node ? node.id : null;
@@ -1690,6 +1722,7 @@ function installSidebarStatusDots() {
1690
1722
  const manualUnread = dsmLoadManual();
1691
1723
  const titleInitializedRows = /* @__PURE__ */ new WeakSet();
1692
1724
  const paint = () => {
1725
+ if (sidebarAdapter.disabled) return;
1693
1726
  const activeId = activeRowId();
1694
1727
  if (activeId !== curActive && activeId && manualUnread.has(activeId)) {
1695
1728
  manualUnread.delete(activeId);
@@ -1752,10 +1785,6 @@ function installSidebarStatusDots() {
1752
1785
  });
1753
1786
  };
1754
1787
  dsmRepaintDots = paint;
1755
- const tick = () => {
1756
- if (++dsmTrashTick % 8 === 0) dsmLoadTrashIds();
1757
- paint();
1758
- };
1759
1788
  let raf = 0;
1760
1789
  const schedulePaint = () => {
1761
1790
  if (raf) return;
@@ -1764,38 +1793,38 @@ function installSidebarStatusDots() {
1764
1793
  paint();
1765
1794
  });
1766
1795
  };
1796
+ const FALLBACK_TICK_MS = 4e3;
1797
+ const tick = () => {
1798
+ if (typeof document !== "undefined" && document.hidden) return;
1799
+ if (++dsmTrashTick % 8 === 0) dsmLoadTrashIds();
1800
+ paint();
1801
+ };
1767
1802
  tick();
1768
1803
  dsmLoadTrashIds();
1769
1804
  paint();
1770
1805
  const obs = new MutationObserver(schedulePaint);
1771
- obs.observe(document.body, { childList: true, subtree: true });
1772
- setInterval(tick, 1200);
1806
+ obs.observe(document.body, {
1807
+ childList: true,
1808
+ subtree: true,
1809
+ attributes: true,
1810
+ attributeFilter: ["data-state", "aria-selected", "class", "style"],
1811
+ characterData: true
1812
+ });
1813
+ setInterval(tick, FALLBACK_TICK_MS);
1773
1814
  }
1774
1815
  function installSidebarWorkspaceDrag() {
1775
1816
  if (typeof document === "undefined") return;
1776
1817
  let sessions = /* @__PURE__ */ new Map();
1777
1818
  let dragging = null;
1778
1819
  let moving = false;
1779
- const findFiber = (el) => {
1780
- const key = Object.keys(el).find((k) => k.startsWith("__reactFiber") || k.startsWith("__reactInternalInstance"));
1781
- return key ? el[key] : null;
1782
- };
1783
- const fiberNodes = (el) => {
1784
- const out = [];
1785
- let fiber = findFiber(el);
1786
- let guard = 0;
1787
- while (fiber && guard++ < 300) {
1788
- const props = fiber.memoizedProps;
1789
- if (props && props.node && typeof props.node === "object") out.push(props.node);
1790
- if (props && props.group && typeof props.group === "object") out.push(props.group);
1791
- fiber = fiber.return;
1792
- }
1793
- return out;
1820
+ const sessionForRow = (row) => {
1821
+ const { node, group } = sidebarAdapter.recognize(row);
1822
+ return sessionForNodes(group ? [node, group].filter(Boolean) : [node], sessions);
1794
1823
  };
1795
- const sessionForRow = (row) => sessionForNodes(fiberNodes(row), sessions);
1796
1824
  const workspaceForRow = (row) => {
1797
1825
  if (sessionForRow(row)) return null;
1798
- return workspaceForNodes(fiberNodes(row));
1826
+ const { group } = sidebarAdapter.recognize(row);
1827
+ return group ? workspaceForNodes([group]) : null;
1799
1828
  };
1800
1829
  const eventRow = (event) => {
1801
1830
  for (const item of event.composedPath ? event.composedPath() : []) {
@@ -1815,6 +1844,7 @@ function installSidebarWorkspaceDrag() {
1815
1844
  document.querySelectorAll(".dsm-drag-source,.dsm-drop-target").forEach((el) => el.classList.remove("dsm-drag-source", "dsm-drop-target"));
1816
1845
  };
1817
1846
  const decorateRows = () => {
1847
+ if (sidebarAdapter.disabled) return;
1818
1848
  document.querySelectorAll('[role="treeitem"]').forEach((row) => {
1819
1849
  const session = sessionForRow(row);
1820
1850
  const ws = workspaceForRow(row);
@@ -1839,7 +1869,8 @@ function installSidebarWorkspaceDrag() {
1839
1869
  maybeRefresh();
1840
1870
  }, true);
1841
1871
  document.addEventListener("dragstart", (event) => {
1842
- const moveCapability = dsmActionCapability("move");
1872
+ if (sidebarAdapter.disabled) return;
1873
+ const moveCapability = dsmActionCapability("relocateSession") || dsmActionCapability("move");
1843
1874
  if (!moveCapability || !moveCapability.available) return;
1844
1875
  const row = eventRow(event);
1845
1876
  const item = row && sessionForRow(row);