@michengai/dsh-archive-manager 1.1.12 → 1.1.14

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/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@
4
4
 
5
5
  Published release notes are retained below; new versions are added without removing earlier entries.
6
6
 
7
+ ## 1.1.14 - 2026-10-26
8
+
9
+ - Add a sidebar session star (#89 by @DSLM): a star sits left of each session title in its own container right after the host's leading slot — always visible once favourited, hover-only otherwise — and reads/writes the plugin's existing favourite set; the session "…" menu gains a matching Favourite / Unfavourite row for keyboard use.
10
+ - Add a "Favourites only" filter (#89 by @DSLM): View options → Filter sessions gains a switch that narrows the sidebar to favourited sessions (empty workspaces and "Expand the remaining N" collapse too, and folded groups are expanded before filtering); the state lives in `localStorage` and no session or archive data is touched.
11
+ - Fix ungrouped session visibility under filter: support host empty-string group key so favourited ungrouped sessions inside folded overflow sections remain discoverable and auto-expand properly.
12
+ - Improve concurrent favourite state reliability: apply delta rollbacks per session id on failure and preserve in-flight pending locks during background adoptions, preventing race-condition UI resets.
13
+ - Align menu item ARIA attributes: adopt `role="menuitemcheckbox"` and `aria-checked` on filter switches to match WAI-ARIA standards.
14
+
15
+ ## 1.1.13 - 2026-10-26
16
+
17
+ - Add opt-in official list-cache rebuilding (#88). Opening Archive Manager checks only stored headers and official cached projections for ordinary cold sessions in registered workspaces, ungrouped lists, or archives, while filtering out special IM channel sessions (`im:`). A repair card appears only when needed. Clicking reads logs sequentially through read-only handles and calls official `coldSnapshot`, without opening sessions, calling models, changing archives/schedules, importing old caches, or writing JSON directly.
18
+ - Accelerate diagnostic scanning: prioritize the host workspace registry's in-memory header snapshot to avoid expensive full-disk JSONL log inspections that can cause client timeouts.
19
+ - Improve operation feedback UX: add a "Done" dismiss button to the rebuild feedback card, and automatically dismiss the panel when refreshing lists if no repair candidates remain.
20
+ - Confirm persistence through bounded official cache readback, retain per-session failures for retry, and refresh the official session list. Rebuilds and deletion share the workspace operation queue; late writes after a confirmation timeout remain ordered before deletion on the official domain chain.
21
+ - Use authoritative per-session persistence `stat` before and after each rebuild read instead of rescanning the entire stored-header list twice per target; retain lifecycle and activity checks and the official cache write/deletion ordering.
22
+ - Detect resolved official list refresh failures from the public list state, including archive/restore/delete refresh paths. Show refresh errors separately without reporting successfully persisted caches as failed saves.
23
+ - Extend isolated compatibility checks to all ten currently published 0.2.x alpha/RC hosts, including real JSONL logs, official JSON cache close/reopen, deletion timing, activity and same-ID lifecycle tests. This does not certify unpublished future hosts.
24
+
7
25
  ## 1.1.12 - 2026-10-24
8
26
 
9
27
  - Upgrade host admission policy: Declare `engines.dsh: ">=0.2.0-rc.1 <0.3.0-0"` to support the entire DeepSeek Harness `0.2.x` line (including `0.2.0-rc.1` onwards, future alphas, RCs, and stable releases) without requiring routine plugin version bumps.
@@ -4,6 +4,24 @@
4
4
 
5
5
  以下发布说明会持续保留;新增版本时不再删除较早记录。
6
6
 
7
+ ## 1.1.14 - 2026-10-26
8
+
9
+ - 新增侧栏会话星标(#89 由 @DSLM 贡献):会话行标题左侧(宿主前导格之后的独立容器)出现收藏星,已收藏常显、未收藏悬停显示,直接读写本插件既有的收藏集合;会话「…」菜单同时新增「收藏 / 取消收藏」,键盘可达。
10
+ - 新增「只看收藏」筛选(#89 由 @DSLM 贡献):视图选项 → 筛选会话 多一行开关,打开后侧栏只保留收藏会话(空分组与「展开其余 N 个」一并收起,折叠组先展开再过滤),状态存 `localStorage`,不改动任何会话或归档数据。
11
+ - 修复未分组收藏会话折叠过滤缺陷:适配宿主未分组组空字符串键(`""`),确证未分组折叠区内的收藏会话在筛选开启时分组标题常驻且能正常自动展开。
12
+ - 增强并发收藏状态机可靠性:写失败时改用单会话差量反向回滚,避免快慢请求交错时整份快照回滚抹掉其他已成功收藏项;保持飞行中并发操作的乐观叠加与防重锁。
13
+ - 规范菜单项无障碍语义:筛选开关采用 `role="menuitemcheckbox"` 与 `aria-checked`,严格符合 WAI-ARIA 规范。
14
+
15
+ ## 1.1.13 - 2026-10-26
16
+
17
+ - 新增显式手动重建官方列表缓存(#88):打开归档管理只检测登记工作区、未分组及归档中的普通冷会话头部和官方缓存,前置排除 IM 渠道特殊会话(`im:`),有候选才显示整理卡片;点击后逐条只读日志并调用官方 `coldSnapshot`。不打开会话、不调用模型、不改变归档或定时任务、不导入旧缓存、不直接写 JSON。
18
+ - 优化诊断读取性能:优先复用官方工作区内存头部快照,避免全库日志 I/O 扫描耗时过长导致客户端超时。
19
+ - 完善操作反馈交互:在缓存重建反馈面板中新增「完成」关闭按钮,并在点击「刷新列表并重新检测」且检测结果为零时自动收起面板,避免反馈卡片常驻阻挡。
20
+ - 通过有界官方缓存读回确认保存,逐条保留失败并支持只重试失败项,完成后刷新官方列表。重建与删除复用工作区操作队列;确认超时的迟到写入仍在官方域链上排在删除之前。
21
+ - 重建读前和读后改用权威持久化 `stat` 定向查询,取消每个目标两次全库头部枚举;保留生命周期、活动复核以及官方缓存写入与删除的顺序。
22
+ - 官方刷新正常返回但列表进入错误状态时也显示刷新错误,归档、恢复、删除的同类刷新共用检测;缓存保存成功与列表刷新失败分别反馈,不误报保存失败。
23
+ - 隔离兼容矩阵补齐当前已发布的十个 0.2.x alpha/RC 宿主,覆盖真实 JSONL、官方 JSON 缓存关闭重开、删除时序、活动及同 ID 生命周期;不把它表述为未来未发布宿主已验证。
24
+
7
25
  ## 1.1.12 - 2026-10-24
8
26
 
9
27
  - 宿主准入机制全面升级:声明 `engines.dsh: ">=0.2.0-rc.1 <0.3.0-0"`,全面开放对 DeepSeek Harness 整个 `0.2.x` 系列的支持(包含 `0.2.0-rc.1` 起及后续全部 alpha/rc/正式版),非破坏性宿主升级无需再重复发版。
package/README.md CHANGED
@@ -36,6 +36,12 @@ Put inactive conversations away and find them again when needed, keeping everyda
36
36
  - **Orphan subagent detection & cleanup**: Automatically detect residue subagents whose parent sessions were externally deleted, displaying disk usage and enabling safe one-click reclamation.
37
37
  - **Controlled online updates & reverse proxy ready**: Features a toggleable online update switch, enterprise host-level configuration locking, and native baseURI dynamic endpoint resolution for prefix-stripping reverse proxies.
38
38
 
39
+ ## Manual list-cache rebuild (#88)
40
+
41
+ Opening Archive Manager checks only headers and official cached projections for ordinary cold sessions in registered workspaces, ungrouped lists, and archives, while excluding special IM channel sessions (`im:`). A repair card appears only for missing/incompatible caches or an unavailable title projection. Click to read complete logs one at a time through read-only handles and rebuild registered projections with official `coldSnapshot`, saving to official `session_projcache`.
42
+
43
+ No old plugin domains are scanned, no JSON is copied, no Session/Agent is activated, no model is called, and archives/schedules remain unchanged. Active sessions are skipped; failed items can be retried individually; feedback provides a "Done" dismiss button and auto-dismisses when clear. The official session list is refreshed and candidates checked again afterward. A persistence-confirmation timeout means unknown, not cancelled: deletion remains ordered after pending native writes. Closing the page stops later requests, not the one already sent.
44
+
39
45
  ## Orphan subagent cleanup
40
46
 
41
47
  When parent sessions are deleted externally or via the host, orphaned subagents may remain on disk. The Archive Manager detects these orphan sessions in the Organizer panel, calculates disk space usage, and provides one-click physical deletion.
package/README.ru.md CHANGED
@@ -32,6 +32,12 @@
32
32
 
33
33
  > В этом README описан выпуск 1.0.0. Полный список возможностей и границы обновления — в [истории изменений](CHANGELOG.md).
34
34
 
35
+ ## Восстановление кеша списков (ещё не выпущено, #88)
36
+
37
+ При открытии менеджера проверяются только заголовки и официальный кеш обычных холодных сессий зарегистрированных рабочих областей и архива. Карточка появляется лишь при отсутствии или несовместимости кеша либо проекции названия. По нажатию полные журналы читаются по одному через read-only дескрипторы, а официальный `coldSnapshot` восстанавливает зарегистрированные проекции в `session_projcache`.
38
+
39
+ Старые домены плагина не сканируются, JSON не копируется, Session/Agent не активируются, модели не вызываются, архивы и расписания не меняются. Активные сессии пропускаются; повторяются только ошибки. После обработки обновляется официальный список и повторяется проверка. Тайм-аут подтверждения не отменяет запись: удаление остаётся в очереди после незавершённой нативной записи. Закрытие страницы останавливает последующие запросы, но не уже отправленный.
40
+
35
41
  ## Очистка подагентов-сирот
36
42
 
37
43
  Когда родительская сессия удалена извне, её дочерние подагенты могут оставаться на диске. Менеджер архива находит такие сессии в панели организации и позволяет физически удалить их в один клик.
package/README.zh-CN.md CHANGED
@@ -36,6 +36,12 @@
36
36
  - **无主孤儿子代理检测与物理清理**:自动探测母会话已被外部物理删除的残留子代理,在会话整理中直观展示空间占用并支持一键物理释放。
37
37
  - **可控在线更新与反向代理适配**:提供独立在线更新开关与企业级宿主配置硬锁定;原生适配反代子路径前缀部署。
38
38
 
39
+ ## 手动重建列表缓存(#88)
40
+
41
+ 打开归档管理后,仅从头部及官方缓存轻量检测登记工作区、未分组及归档中的普通冷会话(前置排除 IM 渠道特殊会话)。官方缓存缺失、生命周期或格式不兼容、或标题投影不可用时,整理区域显示「重建列表缓存」卡片;没有候选就不显示。
42
+
43
+ 点击后逐条只读完整日志,调用官方 `coldSnapshot` 重建全部已注册投影,并保存至官方 `session_projcache`。不扫描旧插件域、不搬运 JSON、不激活会话/Agent、不调用模型,也不改变归档和定时任务。活动会话跳过,失败项可单独重试;操作反馈提供「完成」按钮并支持自动收起;完成后刷新官方列表并重新检测。保存确认超时表示结果未知,不代表已取消;删除操作仍与迟到写入安全排队。关闭页面只停止后续请求,已发送的那一条会在服务端完成。
44
+
39
45
  ## 孤儿子代理清理
40
46
 
41
47
  当母会话在外部或通过系统被物理删除后,其衍生生成的子代理可能残留于磁盘中。归档管理器在「会话整理」面板中会自动检测是否存在此类无主残留,精准识别并展示占用空间,支持一键安全释放磁盘。
@@ -0,0 +1,98 @@
1
+ import { errorMessage } from "./contracts.js";
2
+ import { cacheDiagnosisSchema, cacheRebuildSchema } from "./cache-rebuild-contract.js";
3
+ function createCacheRebuildController(api, options = {}) {
4
+ const { maxLoadRetries = 3, retryDelayMs = 200 } = options;
5
+ let state = { diagnosis: { count: 0, items: [] }, busy: false, progress: null, result: null, error: null, refreshError: null };
6
+ let active = true, sequence = 0, retry = [];
7
+ let retryTimer;
8
+ const listeners = /* @__PURE__ */ new Set();
9
+ const update = (value) => {
10
+ if (!active) return;
11
+ state = { ...state, ...value };
12
+ for (const fn of listeners) fn();
13
+ };
14
+ const load = async (retries = maxLoadRetries) => {
15
+ if (!active) return;
16
+ if (retryTimer !== void 0) {
17
+ clearTimeout(retryTimer);
18
+ retryTimer = void 0;
19
+ }
20
+ const current = ++sequence;
21
+ try {
22
+ const diagnosis = cacheDiagnosisSchema.parse(await api.diagnose());
23
+ if (active && current === sequence) update({ diagnosis, error: null });
24
+ } catch (error) {
25
+ if (retries > 0 && active && current === sequence) {
26
+ retryTimer = setTimeout(() => {
27
+ retryTimer = void 0;
28
+ if (active && current === sequence) void load(retries - 1);
29
+ }, retryDelayMs);
30
+ return;
31
+ }
32
+ if (active && current === sequence) update({ error: errorMessage(error) });
33
+ }
34
+ };
35
+ return {
36
+ snapshot: () => state,
37
+ subscribe(fn) {
38
+ listeners.add(fn);
39
+ return () => {
40
+ listeners.delete(fn);
41
+ };
42
+ },
43
+ load,
44
+ async run(failedOnly = false) {
45
+ if (!active || state.busy) return;
46
+ ++sequence;
47
+ if (retryTimer !== void 0) {
48
+ clearTimeout(retryTimer);
49
+ retryTimer = void 0;
50
+ }
51
+ const items = failedOnly ? [...retry] : [...state.diagnosis.items];
52
+ const result = { rebuiltSessionIds: [], skippedSessionIds: [], failures: [] };
53
+ retry = [];
54
+ update({ busy: true, result: null, error: null, refreshError: null, progress: { done: 0, total: items.length } });
55
+ for (const [index, item] of items.entries()) {
56
+ if (!active) return;
57
+ try {
58
+ const value = cacheRebuildSchema.parse(await api.rebuild({ items: [item] }));
59
+ if (!active) return;
60
+ result.rebuiltSessionIds.push(...value.rebuiltSessionIds);
61
+ result.skippedSessionIds.push(...value.skippedSessionIds);
62
+ result.failures.push(...value.failures);
63
+ if (value.failures.length) retry.push(item);
64
+ } catch (error) {
65
+ if (!active) return;
66
+ result.failures.push({ sessionId: item.sessionId, message: errorMessage(error) });
67
+ retry.push(item);
68
+ }
69
+ update({ progress: { done: index + 1, total: items.length } });
70
+ }
71
+ update({ result });
72
+ try {
73
+ await api.refresh();
74
+ } catch (error) {
75
+ update({ refreshError: errorMessage(error) });
76
+ }
77
+ if (!active) return;
78
+ await load();
79
+ update({ busy: false, progress: null });
80
+ },
81
+ dismiss() {
82
+ if (!active) return;
83
+ update({ result: null, error: null, refreshError: null });
84
+ },
85
+ dispose() {
86
+ active = false;
87
+ ++sequence;
88
+ if (retryTimer !== void 0) {
89
+ clearTimeout(retryTimer);
90
+ retryTimer = void 0;
91
+ }
92
+ listeners.clear();
93
+ }
94
+ };
95
+ }
96
+ export {
97
+ createCacheRebuildController
98
+ };
@@ -0,0 +1,39 @@
1
+ import { record } from "./contracts.js";
2
+ function cacheIdentity(header) {
3
+ return JSON.stringify([header.version, header.createdAt, header.cwd ?? null, header.isSeeded]);
4
+ }
5
+ const candidate = (input) => {
6
+ const value = record(input);
7
+ if (!value || typeof value.sessionId !== "string" || !value.sessionId.length || value.sessionId.length > 1024 || typeof value.identity !== "string" || !value.identity.length || value.identity.length > 4096) throw new TypeError("invalid cache rebuild candidate");
8
+ return { sessionId: value.sessionId, identity: value.identity };
9
+ };
10
+ const cacheRebuildInput = { parse(input) {
11
+ const value = record(input);
12
+ if (!value || !Array.isArray(value.items) || !value.items.length || value.items.length > 20) throw new TypeError("invalid cache rebuild batch (1\u201320 items required)");
13
+ return { items: value.items.map(candidate) };
14
+ } };
15
+ const cacheDiagnosisSchema = { parse(input) {
16
+ const value = record(input);
17
+ if (!value || !Array.isArray(value.items) || value.count !== value.items.length) throw new TypeError("invalid cache diagnosis");
18
+ return { count: value.items.length, items: value.items.map(candidate) };
19
+ } };
20
+ const cacheRebuildSchema = { parse(input) {
21
+ const value = record(input);
22
+ for (const key of ["rebuiltSessionIds", "skippedSessionIds"]) if (!Array.isArray(value?.[key]) || value[key].some((id) => typeof id !== "string")) throw new TypeError("invalid cache rebuild result");
23
+ if (!Array.isArray(value.failures) || value.failures.some((f) => !f || typeof f.sessionId !== "string" || typeof f.message !== "string")) throw new TypeError("invalid cache rebuild failures");
24
+ return value;
25
+ } };
26
+ function cacheRebuildInvocations() {
27
+ const codec = (name, schema) => ({ mode: "strict", typeSymbol: `@michengai/dsh-archive-manager/types#${name}`, create: () => schema, schema });
28
+ return [
29
+ { method: "diagnoseSessionCaches", parameters: [], result: codec("CacheDiagnosis", cacheDiagnosisSchema) },
30
+ { method: "rebuildSessionCaches", parameters: [{ name: "input", wire: "input", source: "json", codec: codec("CacheRebuildInput", cacheRebuildInput) }], result: codec("CacheRebuildResult", cacheRebuildSchema) }
31
+ ].map(({ method, ...rest }) => ({ id: `@michengai/dsh-archive-manager#workspaceRegistry/${method}`, service: "workspaceRegistry", namespace: "workspaceRegistry", method, invocation: { kind: "direct" }, ...rest, sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 } }));
32
+ }
33
+ export {
34
+ cacheDiagnosisSchema,
35
+ cacheIdentity,
36
+ cacheRebuildInput,
37
+ cacheRebuildInvocations,
38
+ cacheRebuildSchema
39
+ };
@@ -0,0 +1,12 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ async function confirmColdCacheWrite(cache, source, { attempts = 100, wait = () => new Promise((resolve) => setTimeout(resolve, 20)) } = {}) {
3
+ const expected = cache.coldSnapshot(source.meta, source.inheritedEventCount, source.events);
4
+ for (let attempt = 0; attempt < attempts; attempt++) {
5
+ if (isDeepStrictEqual(cache.cachedSnapshot(source.meta), expected)) return expected;
6
+ await wait();
7
+ }
8
+ throw new Error("Official cache persistence could not be confirmed; retry is safe only after lifecycle revalidation.");
9
+ }
10
+ export {
11
+ confirmColdCacheWrite
12
+ };