dsh-archived-chats 0.5.0 → 0.5.1

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.md CHANGED
@@ -14,6 +14,10 @@ dsh plugin --profile web add dsh-archived-chats
14
14
 
15
15
  Restart DSH once after installing, then open **Settings → Archived Chats**.
16
16
 
17
+ ## Compatibility
18
+
19
+ Version 0.5.1 is tested against DeepSeek Harness `0.1.0-rc.7`. The plugin registers a top-level `settings.section`, so the rc.7 keyed-slot change for `settings.plugin.item` does not apply to it. Future Harness releases should still be checked with the smoke suite and a real-host UI pass before publishing a plugin update, because client slot and design-token contracts may evolve.
20
+
17
21
  ## Features
18
22
 
19
23
  - **Complete archived-session list**, grouped by workspace (project) with a per-group count. Every group can be collapsed or expanded, and the state is remembered per browser.
@@ -30,7 +34,7 @@ Restart DSH once after installing, then open **Settings → Archived Chats**.
30
34
  - **In-place live deletion**: deleting a resident session replays the agent factory's own disposer sequence — `cancel({ kind: 'disposed' })` → `whenIdle` → `flush` → `agent.scope.dispose()` → detach of the `agents` and `sessions` store entries. The session detach emits `session/disposed`, the persistence coordinator retires (drains and releases) the write path, and the ordinary cold delete completes in the same request. The store entries are internal surfaces, so every step is feature-detected; anything missing falls back to park-and-defer.
31
35
  - **Pending-deletion store** (fallback path and crash bracket): the id is recorded in `$DSH_HOME/plugin-data/archived-chats/pending-deletions.json` while the session stays archived and hidden; the next boot sweeps the queue through the ordinary delete path. In-place deletes are bracketed by the same store (recorded before disposal, cleared once the files are gone), so a crash mid-delete is completed on the next start. Parked sessions are excluded from the listing; unarchiving cancels a pending deletion.
32
36
  - **Title cache**: resolved titles are memoized per id across list refreshes instead of re-reading every archived log; delete and unarchive invalidate their entries.
33
- - **Browser half** (`lib/client.js`) registers a `settings.section` slot entry (order 30) and renders the page with React and DSH design tokens.
37
+ - **Browser half** (`lib/client.js`) registers a `settings.section` slot entry (order 30) and renders the page with React and the rc.7 DSH overlay/state design tokens.
34
38
 
35
39
  ## Development
36
40
 
package/README.zh.md CHANGED
@@ -14,6 +14,10 @@ dsh plugin --profile web add dsh-archived-chats
14
14
 
15
15
  安装后重启一次 DSH,然后打开 **设置 → 已归档的聊天**。
16
16
 
17
+ ## 兼容性
18
+
19
+ 0.5.1 版本以 DeepSeek Harness `0.1.0-rc.7` 为验证基线。插件注册的是顶层 `settings.section`,因此 rc.7 针对 `settings.plugin.item` 的 keyed-slot 变更不影响本插件。以后 Harness 发布新版本时,仍应在发布插件更新前重跑冒烟测试并检查真实宿主页面,因为客户端插槽和设计令牌契约仍可能演进。
20
+
17
21
  ## 功能
18
22
 
19
23
  - **完整归档列表**:按工作区(项目)分组并显示每组数量;每个分组都可折叠/展开,状态按浏览器记忆。
@@ -30,7 +34,7 @@ dsh plugin --profile web add dsh-archived-chats
30
34
  - **活会话原地删除**:删除仍驻留后台的会话时,插件复刻 agent 工厂自身 disposer 的顺序——`cancel({ kind: 'disposed' })` → `whenIdle` → `flush` → `agent.scope.dispose()` → 依次 detach `agents` 与 `sessions` 两个 store 条目;session detach 发出 `session/disposed`,持久化协调器随之 retire(排空并释放)该会话的写入通道,之后冷删除路径在同一请求内完成。所涉 store 条目属于内部接口,每一步都做特性探测,探测失败即回退为停用+延后。
31
35
  - **待删队列**(回退路径与崩溃兜底):id 记入 `$DSH_HOME/plugin-data/archived-chats/pending-deletions.json`,会话保持归档与隐藏;下次启动时插件清扫该队列,通过常规删除路径完成物理删除。原地删除也用该队列包裹(删除前登记、文件移除后清除),中途崩溃由下次启动补完。已入队的会话不会出现在列表中;取消归档会撤销待删标记。
32
36
  - **标题缓存**:列表刷新时按 id 记忆已解析的标题,不再每次全量读日志;删除与取消归档会使对应缓存失效。
33
- - **浏览器半**(`lib/client.js`)注册 `settings.section` 插槽项(order 30),用 React 和 DSH 设计令牌渲染页面。
37
+ - **浏览器半**(`lib/client.js`)注册 `settings.section` 插槽项(order 30),用 React 和 rc.7 的 DSH 浮层/状态设计令牌渲染页面。
34
38
 
35
39
  ## 开发
36
40
 
package/lib/client.js CHANGED
@@ -220,8 +220,8 @@ window.__ModuleLoader__.load({
220
220
  .dac-page{position:relative;display:flex;flex-direction:column;gap:14px;padding:4px 0 28px;font-family:inherit}
221
221
  .dac-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
222
222
  .dac-title{margin:0;color:var(--dsw-alias-label-primary);font-size:18px;font-weight:500;line-height:28px;outline:none}
223
- .dac-deleteall{display:inline-flex;align-items:center;gap:6px;border:none;border-radius:999px;padding:6px 14px;background:rgba(229,72,77,.1);color:#e5484d;font:inherit;font-size:13px;line-height:20px;cursor:pointer;transition:background .15s}
224
- .dac-deleteall:hover:not(:disabled){background:rgba(229,72,77,.18)}
223
+ .dac-deleteall{display:inline-flex;align-items:center;gap:6px;border:none;border-radius:999px;padding:6px 14px;background:transparent;color:var(--dsw-alias-state-error-primary);font:inherit;font-size:13px;line-height:20px;cursor:pointer;transition:background .15s}
224
+ .dac-deleteall:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
225
225
  .dac-deleteall:disabled{opacity:.45;cursor:default}
226
226
  .dac-search{display:flex;align-items:center;gap:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);padding:8px 12px;color:var(--dsw-alias-label-tertiary);transition:border-color .15s}
227
227
  .dac-search:focus-within{border-color:var(--dsw-alias-border-l1,var(--dsw-alias-border-l2))}
@@ -240,7 +240,8 @@ window.__ModuleLoader__.load({
240
240
  .dac-bulk-actions{display:flex;align-items:center;gap:6px}
241
241
  .dac-bulk-btn{border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;padding:5px 11px;cursor:pointer}
242
242
  .dac-bulk-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
243
- .dac-bulk-btn.dac-danger{border-color:rgba(229,72,77,.28);color:#e5484d;background:rgba(229,72,77,.08)}
243
+ .dac-bulk-btn.dac-danger{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:transparent}
244
+ .dac-bulk-btn.dac-danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
244
245
  .dac-bulk-btn:disabled{opacity:.45;cursor:default}
245
246
  .dac-group{display:flex;flex-direction:column;gap:8px;margin-top:6px}
246
247
  .dac-group-head{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:2px}
@@ -255,12 +256,12 @@ window.__ModuleLoader__.load({
255
256
  .dac-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:8px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;transition:background .15s,color .15s;padding:0}
256
257
  .dac-iconbtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
257
258
  .dac-iconbtn:disabled{opacity:.45;cursor:default}
258
- .dac-iconbtn.dac-danger:hover:not(:disabled){color:#e5484d;background:rgba(229,72,77,.1)}
259
- .dac-menu{position:absolute;right:0;top:30px;z-index:40;min-width:150px;display:flex;flex-direction:column;gap:2px;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);box-shadow:0 8px 28px rgba(0,0,0,.18)}
259
+ .dac-iconbtn.dac-danger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger)}
260
+ .dac-menu{position:absolute;right:0;top:30px;z-index:40;min-width:150px;display:flex;flex-direction:column;gap:2px;padding:4px;border:1px solid var(--dsw-alias-border-inverted);border-radius:10px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3)}
260
261
  .dac-menu-item{border:none;border-radius:7px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;padding:7px 10px;cursor:pointer}
261
262
  .dac-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
262
- .dac-menu-item.dac-danger{color:#e5484d}
263
- .dac-menu-item.dac-danger:hover{background:rgba(229,72,77,.1)}
263
+ .dac-menu-item.dac-danger{color:var(--dsw-alias-state-error-primary)}
264
+ .dac-menu-item.dac-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger)}
264
265
  .dac-list{display:flex;flex-direction:column;gap:8px}
265
266
  .dac-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:12px 16px;transition:border-color .15s,background .15s}
266
267
  .dac-row:hover{border-color:var(--dsw-alias-border-l1,var(--dsw-alias-border-l2))}
@@ -276,24 +277,24 @@ window.__ModuleLoader__.load({
276
277
  .dac-empty{display:flex;flex-direction:column;align-items:center;gap:10px;padding:56px 0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}
277
278
  .dac-center{display:flex;align-items:center;justify-content:center;gap:8px;padding:48px 0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}
278
279
  .dac-retry{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;padding:4px 12px;cursor:pointer}
279
- .dac-notice{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid rgba(229,72,77,.45);border-radius:10px;background:rgba(229,72,77,.08);color:#e5484d;font-size:12px;line-height:18px;padding:8px 12px}
280
+ .dac-notice{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid var(--dsw-alias-state-error-primary);border-radius:10px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;padding:8px 12px}
280
281
  .dac-notice button{border:none;background:transparent;color:inherit;cursor:pointer;font:inherit;padding:0;display:inline-flex}
281
- .dac-toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:1200;display:flex;align-items:center;gap:8px;border:1px solid rgba(48,164,108,.4);border-radius:999px;background:color-mix(in srgb,#30a46c 9%,var(--dsw-alias-bg-primary,#fff));color:#2f9e68;font-size:13px;font-weight:500;line-height:20px;padding:7px 12px 7px 14px;box-shadow:0 6px 20px rgba(0,0,0,.10),0 1px 3px rgba(0,0,0,.06);animation:dac-toast-in .28s cubic-bezier(.21,1.02,.55,1) both}
282
+ .dac-toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:1200;display:flex;align-items:center;gap:8px;border:1px solid var(--dsw-alias-state-success-secondary);border-radius:999px;background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary);font-size:13px;font-weight:500;line-height:20px;padding:7px 12px 7px 14px;box-shadow:var(--dsw-shadow-lv3);animation:dac-toast-in .28s cubic-bezier(.21,1.02,.55,1) both}
282
283
  .dac-toast svg{flex:none}
283
284
  .dac-toast button{border:none;background:transparent;color:inherit;opacity:.55;cursor:pointer;font:inherit;padding:2px;margin-left:2px;display:inline-flex;border-radius:999px}
284
- .dac-toast button:hover{opacity:1;background:rgba(48,164,108,.12)}
285
+ .dac-toast button:hover{opacity:1;background:var(--dsw-alias-state-success-secondary)}
285
286
  @keyframes dac-toast-in{from{opacity:0;transform:translateX(-50%) translateY(-10px) scale(.96)}to{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}}
286
287
  @media (prefers-reduced-motion:reduce){.dac-toast{animation:none}}
287
288
  .dac-confirm-overlay{position:fixed;inset:0;z-index:1400;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.4)}
288
- .dac-confirm{width:min(400px,calc(100vw - 64px));display:flex;flex-direction:column;gap:12px;border-radius:16px;background:var(--dsw-alias-bg-layer-2);box-shadow:0 16px 64px rgba(0,0,0,.3);padding:20px;outline:none}
289
- .dac-confirm:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-interactive-primary,#6e6ef7),0 16px 64px rgba(0,0,0,.3)}
289
+ .dac-confirm{width:min(400px,calc(100vw - 64px));display:flex;flex-direction:column;gap:12px;border:1px solid var(--dsw-alias-border-inverted);border-radius:16px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);padding:20px;outline:none}
290
+ .dac-confirm:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-interactive-primary,#6e6ef7),var(--dsw-shadow-lv3)}
290
291
  .dac-confirm-title{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:500;line-height:24px}
291
292
  .dac-confirm-body{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:21px}
292
293
  .dac-confirm-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}
293
294
  .dac-btn{border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;padding:5px 14px;cursor:pointer}
294
295
  .dac-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
295
- .dac-btn-danger{border:none;border-radius:9px;background:#e5484d;color:#fff;font:inherit;font-size:13px;line-height:20px;padding:6px 14px;cursor:pointer}
296
- .dac-btn-danger:hover:not(:disabled){background:#d13438}
296
+ .dac-btn-danger{border:1px solid var(--dsw-alias-state-error-primary);border-radius:9px;background:transparent;color:var(--dsw-alias-state-error-primary);font:inherit;font-size:13px;line-height:20px;padding:5px 14px;cursor:pointer}
297
+ .dac-btn-danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
297
298
  .dac-btn-danger:disabled{opacity:.5;cursor:default}
298
299
  .dac-spin{display:inline-block;width:14px;height:14px;border:2px solid var(--dsw-alias-label-tertiary);border-top-color:transparent;border-radius:50%;animation:dac-rotate .8s linear infinite}
299
300
  @keyframes dac-rotate{to{transform:rotate(360deg)}}
@@ -321,24 +322,35 @@ window.__ModuleLoader__.load({
321
322
  const NAV_LABELS = ["已归档的聊天", "Archived Chats"];
322
323
  const ARCHIVE_ICON_INNER = '<rect x="3" y="4" width="18" height="5" rx="1"></rect><path d="M5 9v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9"></path><path d="M10 13h4"></path>';
323
324
 
325
+ function safeQueryAll(root, selector) {
326
+ try {
327
+ return typeof root?.querySelectorAll === "function" ? root.querySelectorAll(selector) : [];
328
+ } catch {
329
+ return [];
330
+ }
331
+ }
332
+
324
333
  function patchNavIcons() {
325
334
  if (typeof document === "undefined" || document.body === null) return;
326
- const dialogs = document.querySelectorAll('[role="dialog"]');
335
+ const dialogs = safeQueryAll(document, '[role="dialog"]');
327
336
  for (const dialog of dialogs) {
328
- const buttons = dialog.querySelectorAll("nav button");
337
+ const buttons = safeQueryAll(dialog, "nav button");
329
338
  for (const button of buttons) {
330
- const text = button.textContent ?? "";
339
+ const text = typeof button?.textContent === "string" ? button.textContent : "";
331
340
  if (!NAV_LABELS.some((label) => text.includes(label))) continue;
332
- const svg = button.querySelector("svg");
333
- if (svg === null || svg.dataset.dacPatched === "1") continue;
334
- svg.dataset.dacPatched = "1";
335
- svg.setAttribute("viewBox", "0 0 24 24");
336
- svg.setAttribute("fill", "none");
337
- svg.setAttribute("stroke", "currentColor");
338
- svg.setAttribute("stroke-width", "1.5");
339
- svg.setAttribute("stroke-linecap", "round");
340
- svg.setAttribute("stroke-linejoin", "round");
341
- svg.innerHTML = ARCHIVE_ICON_INNER;
341
+ let svg = null;
342
+ try { svg = typeof button.querySelector === "function" ? button.querySelector("svg") : null; } catch { continue; }
343
+ if (svg === null || svg.dataset === void 0 || svg.dataset.dacPatched === "1" || typeof svg.setAttribute !== "function") continue;
344
+ try {
345
+ svg.dataset.dacPatched = "1";
346
+ svg.setAttribute("viewBox", "0 0 24 24");
347
+ svg.setAttribute("fill", "none");
348
+ svg.setAttribute("stroke", "currentColor");
349
+ svg.setAttribute("stroke-width", "1.5");
350
+ svg.setAttribute("stroke-linecap", "round");
351
+ svg.setAttribute("stroke-linejoin", "round");
352
+ svg.innerHTML = ARCHIVE_ICON_INNER;
353
+ } catch { /* keep the host-provided fallback icon */ }
342
354
  }
343
355
  }
344
356
  }
@@ -347,15 +359,22 @@ window.__ModuleLoader__.load({
347
359
 
348
360
  function startNavIconPatch() {
349
361
  patchNavIcons();
350
- const Observer = typeof window !== "undefined" ? window.MutationObserver : void 0;
362
+ let Observer;
363
+ try { Observer = typeof window !== "undefined" ? window.MutationObserver : void 0; } catch { return; }
351
364
  if (Observer === void 0 || typeof document === "undefined" || document.body === null) return;
352
365
  if (navObserver !== null) return;
353
- navObserver = new Observer(() => patchNavIcons());
354
- navObserver.observe(document.body, { childList: true, subtree: true });
366
+ let observer = null;
367
+ try {
368
+ observer = new Observer(() => patchNavIcons());
369
+ observer.observe(document.body, { childList: true, subtree: true });
370
+ navObserver = observer;
371
+ } catch {
372
+ try { observer?.disconnect?.(); } catch { /* best-effort decoration cleanup */ }
373
+ }
355
374
  }
356
375
 
357
376
  function stopNavIconPatch() {
358
- navObserver?.disconnect();
377
+ try { navObserver?.disconnect?.(); } catch { /* best-effort decoration cleanup */ }
359
378
  navObserver = null;
360
379
  }
361
380
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-archived-chats",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "DeepSeek Harness 已归档会话管理页:搜索、排序、批量选择、取消归档和删除。An Archived Chats settings page for DeepSeek Harness with search, sorting, bulk selection, unarchive, and delete.",
5
5
  "license": "MIT",
6
6
  "author": "Ultronen",