dsh-sessions-manager 3.4.2 → 3.5.0

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
@@ -14,6 +14,8 @@
14
14
 
15
15
  A persistent DSH plugin (host + browser halves). Starting with v3.0.0, it covers both the **settings panel** and the **main sidebar**, so frequent session actions don't require opening Settings.
16
16
 
17
+ v3.5.0 adds a two-generation DSH persistence adapter. It understands both legacy header lists/`readFrom` and the new snapshot/`SessionHandle` API, and reports action-level capabilities to the UI. When a runtime has no verified safe purge or migration path, the corresponding controls explain why they are disabled and the Host rejects direct calls, preventing false success messages.
18
+
17
19
  ## Features
18
20
 
19
21
  ### Settings panel: Session Manager
@@ -149,6 +151,7 @@ lib/client.js pre-built client (ModuleLoader CJS handshake)
149
151
  | POST | `/archived-sessions/storage` | Storage-usage aggregation (per-workspace ranking + largest sessions) `{ topN }` |
150
152
  | POST | `/archived-sessions/auto-archive/settings` | Read or update the auto-archive policy `{ inactiveDays, skipStarred }`; reading lazily triggers the daily check |
151
153
  | POST | `/archived-sessions/auto-archive/run` | Run a one-off auto-archive check immediately (ignores the daily throttle) |
154
+ | POST | `/archived-sessions/capabilities` | Report the persistence generation and per-action read/archive/trash/purge/cross-workspace-move capabilities |
152
155
 
153
156
  > Deleting a session moves it to the recycle bin by default; only "permanently delete" inside the recycle bin physically removes the log. Permanently deleted sessions are hidden forever on the client side so they don't reappear in the sidebar or "ungrouped" due to DSH runtime caching.
154
157
 
@@ -157,6 +160,9 @@ lib/client.js pre-built client (ModuleLoader CJS handshake)
157
160
  - **Supported platforms**: cross-platform (macOS / Windows / Linux) — wherever DSH runs the plugin runs; the host half is Node (about `^22.19` or `>=24`) and the client is React, with no OS-specific APIs.
158
161
  - Works in both DSH Desktop and DSH web (same host + client halves).
159
162
  - Peer dependencies are listed in `package.json`; `react` and `@deepseek-ai/*` are provided by the DSH runtime.
163
+ - `0.1.2-rc.1`: the existing read, archive, recycle-bin, permanent-purge, and cross-workspace move paths remain available.
164
+ - `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.
165
+ - Unverified future runtimes expose only capabilities the plugin can safely identify; method presence alone is not presented as behavioral compatibility.
160
166
 
161
167
  ## License
162
168
 
package/README.md CHANGED
@@ -14,6 +14,8 @@
14
14
 
15
15
  一个 DSH 持久化插件(host + browser 双半)。v3.0.0 起同时覆盖「设置面板」与「主页侧边栏」两个入口,无需打开设置即可完成高频会话操作。
16
16
 
17
+ v3.5.0 新增 DSH 双代持久化适配:同时识别旧式 header 列表/`readFrom` 与新版 snapshot/`SessionHandle`,并向界面报告每项操作的实际能力。当前 Runtime 没有经过验证的安全删除或迁移路径时,相关按钮会显示禁用原因,Host 也会拒绝调用,避免出现“提示成功但日志没有删除/移动”的假成功。
18
+
17
19
  ## 功能
18
20
 
19
21
  ### 设置面板:会话管理
@@ -149,6 +151,7 @@ lib/client.js 预构建 client(ModuleLoader CJS handshake)
149
151
  | POST | `/archived-sessions/storage` | 存储占用聚合(按工作区排行 + 最大的会话)`{ topN }` |
150
152
  | POST | `/archived-sessions/auto-archive/settings` | 读取或更新自动归档策略 `{ inactiveDays, skipStarred }`;读取时惰性触发每日检查 |
151
153
  | POST | `/archived-sessions/auto-archive/run` | 立即执行一次自动归档检查(忽略每日节流) |
154
+ | POST | `/archived-sessions/capabilities` | 返回当前持久化代际及读取、归档、回收站、永久删除、跨工作区移动能力 |
152
155
 
153
156
  > 删除会话默认进入回收站,只有回收站内的「彻底删除」才会物理移除日志。被彻底删除的会话由前端永久隐藏,避免 DSH 运行时缓存使其重新出现在侧栏或「未分组」中。
154
157
 
@@ -157,6 +160,9 @@ lib/client.js 预构建 client(ModuleLoader CJS handshake)
157
160
  - **适配系统**:跨平台(macOS / Windows / Linux)——只要 DSH 能在该系统运行即可;本插件 host 基于 Node(约 `^22.19` 或 `>=24`)、浏览器端为 React,不依赖特定操作系统 API。
158
161
  - DSH Desktop / web 均可(同一套 host + client)。
159
162
  - peerDependencies 见 `package.json`;`react`、`@deepseek-ai/*` 由 DSH 运行时提供。
163
+ - `0.1.2-rc.1`:现有读取、归档、回收站、永久删除和跨工作区移动能力保持可用。
164
+ - `0.1.3-alpha.1`:支持 snapshot 列表和 `SessionHandle` 只读流程;永久删除与跨工作区移动在缺少已验证安全路径时自动禁用,其余管理能力继续工作。能力以面板实际提示为准。
165
+ - 未经验证的未来 Runtime 默认只开放能够识别的安全能力;插件不会用方法存在与否冒充行为兼容。
160
166
 
161
167
  ## License
162
168
 
package/lib/client.js CHANGED
@@ -278,6 +278,7 @@ function installSettingsNavIcons(ctx) {
278
278
  function SessionPanel({ workspacesSvc }) {
279
279
  const [sessions, setSessions] = (0, import_react.useState)(null);
280
280
  const [workspaces, setWorkspaces] = (0, import_react.useState)([]);
281
+ const [capabilities, setCapabilities] = (0, import_react.useState)(null);
281
282
  const initialPrefs = (0, import_react.useRef)(loadPanelPrefs()).current;
282
283
  const [filter, setFilter] = (0, import_react.useState)(() => ["all", "active", "archived", "starred", "trash"].includes(initialPrefs.filter) ? initialPrefs.filter : "all");
283
284
  const [query, setQuery] = (0, import_react.useState)("");
@@ -326,12 +327,14 @@ function SessionPanel({ workspacesSvc }) {
326
327
  setError(null);
327
328
  Promise.all([
328
329
  postJSON("/archived-sessions/sessions", {}),
329
- postJSON("/archived-sessions/workspaces", {})
330
- ]).then(([s, works]) => {
330
+ postJSON("/archived-sessions/workspaces", {}),
331
+ postJSON("/archived-sessions/capabilities", {})
332
+ ]).then(([s, works, caps]) => {
331
333
  const purged = dsmLoadPurged();
332
334
  const visible = (s.items || []).filter((x) => !purged.has(String(x.sessionId)));
333
335
  setSessions(visible);
334
336
  setWorkspaces(works.items || []);
337
+ setCapabilities(caps || null);
335
338
  setSelected({});
336
339
  setDelTarget(null);
337
340
  setConfirmBatch(false);
@@ -428,6 +431,9 @@ function SessionPanel({ workspacesSvc }) {
428
431
  const w = workspaces.find((x) => x.workspaceId === id);
429
432
  return w ? w.path : "";
430
433
  };
434
+ 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" };
435
+ const canPurge = actionCapability("purge");
436
+ const canMove = actionCapability("move");
431
437
  const archivedList = sessions ? sessions.filter((x) => x.archived) : [];
432
438
  const activeList = sessions ? sessions.filter((x) => !x.archived) : [];
433
439
  const starredList = starredOf(sessions);
@@ -511,7 +517,9 @@ function SessionPanel({ workspacesSvc }) {
511
517
  postJSON("/archived-sessions/trash/verify", {}).then((r) => {
512
518
  setTrashBusy(null);
513
519
  setTrashCheck(r);
514
- showToast(r.missing ? `\u53D1\u73B0 ${r.missing} \u6761\u65E5\u5FD7\u7F3A\u5931` : "\u56DE\u6536\u7AD9\u6821\u9A8C\u901A\u8FC7");
520
+ if (r.missing) showToast(`\u53D1\u73B0 ${r.missing} \u6761\u65E5\u5FD7\u7F3A\u5931`);
521
+ else if (r.unverified) showToast(`${r.unverified} \u6761\u65E5\u5FD7\u4F4D\u7F6E\u7531\u5F53\u524D Runtime \u7BA1\u7406\uFF0C\u65E0\u6CD5\u76F4\u63A5\u6838\u9A8C`);
522
+ else showToast("\u56DE\u6536\u7AD9\u6821\u9A8C\u901A\u8FC7");
515
523
  }).catch((e) => {
516
524
  setTrashBusy(null);
517
525
  setError(String(e && e.message || e));
@@ -612,6 +620,10 @@ function SessionPanel({ workspacesSvc }) {
612
620
  });
613
621
  };
614
622
  const doMove = (it) => {
623
+ if (!canMove.available) {
624
+ setError(canMove.reason || "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u8DE8\u5DE5\u4F5C\u533A\u79FB\u52A8");
625
+ return;
626
+ }
615
627
  const targetPath = moveMode === "new" ? newPath.trim() : wsPath(targetWs);
616
628
  if (!targetPath) {
617
629
  setError("\u8BF7\u9009\u62E9\u5DF2\u6709\u5DE5\u4F5C\u533A\u6216\u8F93\u5165\u65B0\u7684\u76EE\u6807\u76EE\u5F55\u8DEF\u5F84");
@@ -653,6 +665,10 @@ function SessionPanel({ workspacesSvc }) {
653
665
  });
654
666
  };
655
667
  const openMoveFor = (it) => {
668
+ if (!canMove.available) {
669
+ setError(canMove.reason || "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u8DE8\u5DE5\u4F5C\u533A\u79FB\u52A8");
670
+ return;
671
+ }
656
672
  if (openMove === it.sessionId) {
657
673
  setOpenMove(null);
658
674
  return;
@@ -738,17 +754,22 @@ function SessionPanel({ workspacesSvc }) {
738
754
  children: "\u22EF"
739
755
  }
740
756
  ),
741
- openMenu === it.sessionId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "more-menu", role: "menu", children: items.map(([id, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
742
- "button",
743
- {
744
- type: "button",
745
- role: "menuitem",
746
- className: "more-item" + (id === "delete" ? " more-item-danger" : ""),
747
- onClick: () => runMenu(id, it),
748
- children: label
749
- },
750
- id
751
- )) })
757
+ openMenu === it.sessionId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "more-menu", role: "menu", children: items.map(([id, label]) => {
758
+ const blocked = id === "move" && !canMove.available;
759
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
760
+ "button",
761
+ {
762
+ type: "button",
763
+ role: "menuitem",
764
+ className: "more-item" + (id === "delete" ? " more-item-danger" : ""),
765
+ disabled: blocked,
766
+ title: blocked ? canMove.reason : void 0,
767
+ onClick: () => runMenu(id, it),
768
+ children: label
769
+ },
770
+ id
771
+ );
772
+ }) })
752
773
  ] });
753
774
  };
754
775
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv", role: "region", "aria-label": "\u4F1A\u8BDD\u7BA1\u7406", children: [
@@ -1191,22 +1212,25 @@ function SessionPanel({ workspacesSvc }) {
1191
1212
  ] }),
1192
1213
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-field", children: [
1193
1214
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u6C38\u4E45\u6E05\u7406" }),
1194
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: trashBusy !== null || !trash.length, onClick: () => setPurgeTarget("__all"), children: "\u6E05\u7A7A\u56DE\u6536\u7AD9" })
1215
+ /* @__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" })
1195
1216
  ] })
1196
1217
  ] }),
1218
+ !canPurge.available && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-empty", role: "status", children: canPurge.reason }),
1197
1219
  trashCheck && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: trashCheck.missing ? "archv-err" : "archv-empty", role: "status", children: [
1198
1220
  "\u6821\u9A8C\u5B8C\u6210\uFF1A",
1199
1221
  trashCheck.healthy,
1200
1222
  " \u6761\u6B63\u5E38\uFF0C",
1201
1223
  trashCheck.missing,
1202
- " \u6761\u65E5\u5FD7\u7F3A\u5931\u3002"
1224
+ " \u6761\u65E5\u5FD7\u7F3A\u5931\uFF0C",
1225
+ trashCheck.unverified || 0,
1226
+ " \u6761\u65E0\u6CD5\u76F4\u63A5\u6838\u9A8C\u3002"
1203
1227
  ] }),
1204
1228
  trash.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsm-trash-empty", children: "\u56DE\u6536\u7AD9\u4E3A\u7A7A\u3002\u5220\u9664\u7684\u4F1A\u8BDD\u4F1A\u5148\u8FDB\u5165\u8FD9\u91CC\uFF0C\u53EF\u6062\u590D\u6216\u5F7B\u5E95\u5220\u9664\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsm-trash-list", children: trash.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsm-trash-row", children: [
1205
1229
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-trash-name", title: t.sessionId, children: t.title || t.sessionId }),
1206
1230
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-trash-date", children: fmtDate(t.deletedAt) }),
1207
1231
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-trash-actions", children: [
1208
1232
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: trashBusy !== null, onClick: () => restoreTrash(t.sessionId), children: "\u6062\u590D" }),
1209
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: trashBusy !== null, onClick: () => setPurgeTarget(t.sessionId), children: "\u5F7B\u5E95\u5220\u9664" })
1233
+ /* @__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" })
1210
1234
  ] })
1211
1235
  ] }, t.sessionId)) })
1212
1236
  ] }),
@@ -1319,6 +1343,17 @@ var dsmTrashIds = null;
1319
1343
  var dsmServerPurgedIds = /* @__PURE__ */ new Set();
1320
1344
  var dsmAuthoritativeTitles = /* @__PURE__ */ new Map();
1321
1345
  var dsmTrashTick = 0;
1346
+ var dsmCapabilities = null;
1347
+ async function dsmLoadCapabilities() {
1348
+ try {
1349
+ dsmCapabilities = await postJSON("/archived-sessions/capabilities", {});
1350
+ } catch (e) {
1351
+ }
1352
+ return dsmCapabilities;
1353
+ }
1354
+ function dsmActionCapability(name) {
1355
+ return dsmCapabilities && dsmCapabilities.actions && dsmCapabilities.actions[name];
1356
+ }
1322
1357
  async function dsmLoadTrashIds() {
1323
1358
  try {
1324
1359
  const r = await postJSON("/archived-sessions/sidebar-state", {});
@@ -1549,6 +1584,11 @@ function installSidebarSessionMenuAug() {
1549
1584
  const move = mk("\u79FB\u52A8\u4F1A\u8BDD", ICON_MOVE, false);
1550
1585
  const del = mk("\u5220\u9664\u4F1A\u8BDD", ICON_DEL, true);
1551
1586
  const mark = mk(dsmLoadManual().has(info.id) ? "\u6807\u8BB0\u5DF2\u8BFB" : "\u6807\u8BB0\u672A\u8BFB", ICON_UNREAD, false);
1587
+ const moveCapability = dsmActionCapability("move");
1588
+ if (!moveCapability || !moveCapability.available) {
1589
+ move.btn.disabled = true;
1590
+ move.btn.title = moveCapability && moveCapability.reason || "\u6B63\u5728\u68C0\u67E5\u5F53\u524D\u7248\u672C\u7684\u79FB\u52A8\u80FD\u529B";
1591
+ }
1552
1592
  if (mark.btn.firstChild) mark.btn.firstChild.style.color = "var(--dsw-alias-state-business-primary)";
1553
1593
  const chev = document.createElement("span");
1554
1594
  chev.style.cssText = "margin-left:auto;flex:none;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:1";
@@ -1785,6 +1825,8 @@ function installSidebarWorkspaceDrag() {
1785
1825
  maybeRefresh();
1786
1826
  }, true);
1787
1827
  document.addEventListener("dragstart", (event) => {
1828
+ const moveCapability = dsmActionCapability("move");
1829
+ if (!moveCapability || !moveCapability.available) return;
1788
1830
  const row = eventRow(event);
1789
1831
  const item = row && sessionForRow(row);
1790
1832
  if (!item || moving) return;
@@ -1864,6 +1906,7 @@ function installSidebarWorkspaceDrag() {
1864
1906
  }
1865
1907
  }
1866
1908
  function apply(ctx) {
1909
+ dsmLoadCapabilities();
1867
1910
  installSettingsNavIcons(ctx);
1868
1911
  installSidebarSessionMenuAug();
1869
1912
  installSidebarStatusDots();