dsh-archived-chats 1.2.0 → 1.3.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.md CHANGED
@@ -57,20 +57,20 @@ dsh plugin --profile web update dsh-archived-chats
57
57
  | **Browse and search** | Workspace-grouped archive browsing, full-text search across messages and tool results, filters, sorting, tags, and notes. |
58
58
  | **Archive a workspace** | From **Settings → Session Archive**, choose a workspace and open one confirmation for every eligible chat in it. Empty new-session windows are excluded; the workspace and its directory stay unchanged. |
59
59
  | **Read-only preview** | Native conversation layout for Markdown, reasoning, tool activity, JSON, code, and available stored images, with responsive turn navigation. |
60
- | **Existing data** | Prior snapshots remain accessible under **Storage & Retention → Legacy data** for read-only preview, recovery as an archived copy, or confirmed deletion. Archiving no longer creates versions. |
60
+ | **Existing data** | Prior snapshots appear directly in the **Recycle Bin** for read-only preview, recovery as a new archived copy, or confirmed permanent deletion. Archiving no longer creates versions. |
61
61
  | **Backup and restore** | JSON + Markdown ZIP export with preview-first, conflict-safe import. Existing session IDs are never overwritten. |
62
62
  | **Recoverable deletion** | Snapshot-protected Recycle Bin with immediate Undo, two-level restore, separately confirmed permanent deletion, and an optional direct permanent-delete action for users who do not need recovery. |
63
- | **Storage and relationships** | Separate storage accounting, preview-first Recycle Bin age policies, and read-only Origins & Branches for forks and subagent trees. |
63
+ | **Storage and relationships** | Separate storage accounting, optional automatic Recycle Bin cleanup, and read-only Origins & Branches for forks and subagent trees. |
64
64
 
65
- The four main views are **Archived**, **Recycle Bin**, **Storage & Retention**, and **Origins & Branches**. Existing snapshots are preserved during upgrades; the separate History tab and archive-time capture have been retired.
65
+ The four main views are **Archived**, **Recycle Bin**, **Storage & Retention**, and **Origins & Branches**. Existing snapshots are preserved during upgrades and folded into the Recycle Bin; the separate History entry and archive-time capture have been retired.
66
66
 
67
67
  ## Safety by design
68
68
 
69
69
  - **Local only:** plugin metadata, recycle records, policies, and validated snapshots stay under `$DSH_HOME/plugin-data/archived-chats/`. Nothing is uploaded or cloud-synced.
70
- - **No silent overwrite:** imports and legacy-data recovery create or select non-conflicting IDs; they never replace an existing session.
70
+ - **No silent overwrite:** imports and legacy-snapshot recovery create or select non-conflicting IDs; they never replace an existing session.
71
71
  - **Deletion stays explicit:** ordinary removal enters the Recycle Bin after snapshot protection. Physical removal is available only through confirmed permanent-purge actions.
72
- - **No automatic cleanup:** retention policies are saved separately from execution. Every cleanup starts with a short-lived preview and explicit selection.
73
- - **Confirmed workspace archive:** the plugin prepares the exact set in the background, then shows one confirmation with the workspace and chat count. Only sessions whose inspected log contains a real `turn/start` are eligible; empty new-session windows and sessions whose content cannot be confirmed are skipped. Its five-minute, single-use credential excludes chats added later; a running chat is skipped, never stopped or moved.
72
+ - **Optional automatic cleanup:** off by default. After confirming a retention period, expired Recycle Bin chats are permanently deleted while DSH runs, with catch-up on startup. Enabling or shortening the period shows affected chats for confirmation; older saved policies stay disabled.
73
+ - **Confirmed workspace archive:** the chooser lists only workspaces with eligible chats and supports single, multiple, or toggleable Select all selection through a bottom-right Confirm button. The plugin prepares each exact set in the background, skips workspaces that became empty, then shows one aggregate confirmation. Only sessions whose inspected log contains a real `turn/start` are eligible; empty new-session windows and sessions whose content cannot be confirmed are skipped. Each workspace keeps its own five-minute, single-use credential, which excludes chats added later; a running chat is skipped, never stopped or moved.
74
74
  - **Backup scope is visible:** ZIP exports preserve complete session JSON and readable Markdown, but do not include attachment bytes or descendant sessions.
75
75
 
76
76
  ## Compatibility
@@ -79,14 +79,14 @@ Features activate from the public capabilities exposed by the DeepSeek Harness H
79
79
 
80
80
  | Host capability | Plugin behavior |
81
81
  | --- | --- |
82
- | Archive and session reads | Browsing, search, preview, legacy-data inventory, storage accounting, and lineage. |
83
- | `settings.section` + public `archiveSession` | The plugin-owned settings page provides the workspace chooser and one-confirmation archive flow without requiring a workspace-menu extension slot. Without archive capability, preparation returns `workspace-archive-unsupported` and makes no change. |
82
+ | Archive and session reads | Browsing, search, preview, legacy-snapshot inventory in the Recycle Bin, storage accounting, and lineage. |
83
+ | `settings.section` + public `archiveSession` | The plugin-owned settings page provides an eligible-only multi-workspace chooser and one aggregate confirmation without requiring a workspace-menu extension slot. Without archive capability, preparation returns `workspace-archive-unsupported` and makes no change. |
84
84
  | Attachment reads | Stored images appear in conversation and snapshot previews; without it, text remains readable. |
85
85
  | Session-scoped log location | Recycle Bin permanent deletion uses the persistence provider's public `locate(meta)` capability. Providers without a session-scoped location remain unsupported; failed operations retain their rows and display the reason. |
86
- | Public session writer | ZIP import, legacy-data recovery, and snapshot fallback when an original is missing all write through the Host's public `create` / `append` / `locate` capability, or a dedicated restore entry point where one exists. |
86
+ | Public session writer | ZIP import, legacy-snapshot recovery, and snapshot fallback when an original is missing all write through the Host's public `create` / `append` / `locate` capability, or a dedicated restore entry point where one exists. |
87
87
  | Missing write capability | The operation returns `restore-unsupported` without writing or overwriting data. |
88
88
 
89
- Back up `$DSH_HOME/plugin-data/archived-chats/` before downgrading to a release that does not display legacy data or understand recycle snapshots.
89
+ Back up `$DSH_HOME/plugin-data/archived-chats/` before downgrading to a release that does not understand the unified Recycle Bin or newer snapshot state.
90
90
 
91
91
  ## Demo preview
92
92
 
@@ -101,12 +101,12 @@ The eight fixed screenshots below come from an isolated Simplified Chinese light
101
101
  <td><img src="assets/screenshots/preview-02.png" alt="Full-text search, filters, tags, and readable hit excerpts"><br><sub>Full-text search</sub></td>
102
102
  </tr>
103
103
  <tr>
104
- <td><img src="assets/screenshots/preview-03.png" alt="Native read-only legacy-data preview with a stored image"><br><sub>Read-only preview</sub></td>
105
- <td><img src="assets/screenshots/preview-04.png" alt="Legacy data with recovery and deletion actions"><br><sub>Legacy data</sub></td>
104
+ <td><img src="assets/screenshots/preview-03.png" alt="Native read-only snapshot preview with a stored image"><br><sub>Read-only preview</sub></td>
105
+ <td><img src="assets/screenshots/preview-04.png" alt="Existing snapshots unified into the Recycle Bin"><br><sub>Unified Recycle Bin</sub></td>
106
106
  </tr>
107
107
  <tr>
108
- <td><img src="assets/screenshots/preview-05.png" alt="Irreversible confirmation before clearing legacy data"><br><sub>Clear legacy data confirmation</sub></td>
109
- <td><img src="assets/screenshots/preview-06.png" alt="Recycle Bin protection snapshot, restore, and permanent deletion"><br><sub>Recycle Bin</sub></td>
108
+ <td><img src="assets/screenshots/preview-05.png" alt="Irreversible confirmation before emptying the Recycle Bin"><br><sub>Empty Recycle Bin confirmation</sub></td>
109
+ <td><img src="assets/screenshots/preview-06.png" alt="Recycle Bin row and workspace restore and permanent-delete actions"><br><sub>Recycle Bin actions</sub></td>
110
110
  </tr>
111
111
  <tr>
112
112
  <td><img src="assets/screenshots/preview-07.png" alt="Storage accounting and retention policy controls"><br><sub>Storage and retention</sub></td>
@@ -135,7 +135,7 @@ Session Archive is actively maintained. The latest stable npm release receives f
135
135
  npm test
136
136
  ```
137
137
 
138
- The suite covers Host and browser behavior, export/import, legacy-data recovery, Recycle Bin, retention, search, responsive layout, public types, package contents, and repository hygiene. It uses isolated temporary data and never reads real sessions.
138
+ The suite covers Host and browser behavior, export/import, older-snapshot recovery, Recycle Bin, retention, search, responsive layout, public types, package contents, and repository hygiene. It uses isolated temporary data and never reads real sessions.
139
139
 
140
140
  ## Uninstall
141
141
 
@@ -143,7 +143,7 @@ The suite covers Host and browser behavior, export/import, legacy-data recovery,
143
143
  dsh plugin --profile web remove dsh-archived-chats
144
144
  ```
145
145
 
146
- Uninstalling removes only the plugin package. It does not delete local data under `$DSH_HOME/plugin-data/archived-chats/` or trigger Recycle Bin permanent purge. Retained data includes `metadata.json`, `trash.json`, `retention.json`, the `snapshots/` directory, and any legacy `pending-deletions.json` that has not yet been migrated. A later reinstall can use this data. Before permanently removing the directory, restore and back up anything you need, then delete it manually only after confirming that none of its data is still required.
146
+ Uninstalling removes only the plugin package. It does not delete local data under `$DSH_HOME/plugin-data/archived-chats/` or trigger Recycle Bin permanent purge. Retained data includes `metadata.json`, `trash.json`, `legacy-recycle.json`, `retention.json`, the `snapshots/` directory, and any legacy `pending-deletions.json` that has not yet been migrated. A later reinstall can use this data. Before permanently removing the directory, restore and back up anything you need, then delete it manually only after confirming that none of its data is still required.
147
147
 
148
148
  ## License
149
149
 
package/README.zh-CN.md CHANGED
@@ -56,20 +56,20 @@ dsh plugin --profile web update dsh-archived-chats
56
56
  | **浏览与搜索** | 按工作区浏览归档聊天,全文搜索消息和工具结果,并支持筛选、排序、标签与备注。 |
57
57
  | **归档整个工作区** | 从 **设置 → 会话档案** 打开工作区选择器,再用一次确认归档其中全部符合条件的聊天;空白的新会话窗口不会计入,工作区及其目录保持不变。 |
58
58
  | **原生只读预览** | 以原生对话布局展示 Markdown、思考过程、工具活动、JSON、代码和可用的已存储图片,并提供响应式轮次导航。 |
59
- | **旧版数据** | 已有快照保留在 **空间与策略 → 旧版数据**,支持只读预览、恢复为归档副本或确认删除;归档不再生成版本。 |
59
+ | **已有快照** | 已有快照直接显示在 **回收站**,支持只读预览、恢复为新的归档副本或确认永久删除;归档不再生成版本。 |
60
60
  | **备份与恢复** | 导出 JSON + Markdown ZIP,并通过预览优先、冲突安全的流程导入;已有会话 ID 永不覆盖。 |
61
61
  | **可恢复删除** | 带保护快照的回收站支持立即撤销、两级恢复,以及单独确认的永久删除。 |
62
- | **空间与关系** | 空间分账、预览优先的回收站保留天数策略,以及用于分叉和子代理树的只读「来源与分支」。 |
62
+ | **空间与关系** | 空间分账、可选的回收站自动清理,以及用于分叉和子代理树的只读「来源与分支」。 |
63
63
 
64
- 四个主视图为 **已归档**、**回收站**、**空间与策略** 和 **来源与分支**。升级会保留已有快照;独立历史版本标签与归档时自动抓取已取消。
64
+ 四个主视图为 **已归档**、**回收站**、**空间与策略** 和 **来源与分支**。升级会保留已有快照并统一放入回收站;独立历史版本入口与归档时自动抓取已取消。
65
65
 
66
66
  ## 安全设计
67
67
 
68
68
  - **数据只在本机:** 插件元数据、回收记录、策略和已验证快照均保存在 `$DSH_HOME/plugin-data/archived-chats/`,不会上传或云同步。
69
- - **不静默覆盖:** 导入和旧版数据恢复只创建或选择无冲突 ID,绝不覆盖已有会话。
69
+ - **不静默覆盖:** 导入和旧快照恢复只创建或选择无冲突 ID,绝不覆盖已有会话。
70
70
  - **删除必须明确:** 普通移除会在快照保护后进入回收站;只有经过确认的永久删除操作才会物理清除。
71
- - **不自动清理:** 保留策略的保存与执行分离,每次清理都从短效预览和明确选择开始。
72
- - **确认工作区归档:** 插件在后台准备精确集合,再显示一次包含工作区与会话数量的确认。只有检查到真实 `turn/start` 的会话才符合条件;空白的新会话窗口及无法确认内容的会话都会跳过。5 分钟有效、只能使用一次的凭据不会纳入之后新建的聊天;运行中的聊天只会跳过,绝不停止或移动。
71
+ - **可选自动清理:** 默认关闭,确认保留期限后,DSH 运行时自动永久删除到期的回收聊天,启动后补清理。开启或缩短期限会先列出受影响聊天供确认;旧版设置不会自动开启。
72
+ - **确认工作区归档:** 选择器只显示有可归档会话的工作区,支持单选、多选和可再次点击取消的“全选”,并统一从右下角“确定”继续。插件在后台分别准备精确集合,跳过准备期间变为空的工作区,再显示一次汇总确认。只有检查到真实 `turn/start` 的会话才符合条件;空白的新会话窗口及无法确认内容的会话都会跳过。每个工作区保留独立的 5 分钟单次凭据,不会纳入之后新建的聊天;运行中的聊天只会跳过,绝不停止或移动。
73
73
  - **备份范围清楚:** ZIP 保留完整会话 JSON 和可读 Markdown,但不包含附件二进制或后代会话。
74
74
 
75
75
  ## 兼容性
@@ -78,14 +78,14 @@ dsh plugin --profile web update dsh-archived-chats
78
78
 
79
79
  | Host 能力 | 插件行为 |
80
80
  | --- | --- |
81
- | 归档与会话读取 | 浏览、搜索、预览、旧版数据清单、空间分账和会话血缘。 |
82
- | `settings.section` + 公开 `archiveSession` | 插件自己的设置页提供工作区选择器和一次确认归档流程,不依赖工作区菜单扩展 slot;缺少归档能力时,准备请求返回 `workspace-archive-unsupported`,且不作任何修改。 |
81
+ | 归档与会话读取 | 浏览、搜索、预览、回收站中的旧快照清单、空间分账和会话血缘。 |
82
+ | `settings.section` + 公开 `archiveSession` | 插件自己的设置页提供仅展示可归档项的多工作区选择器和一次汇总确认,不依赖工作区菜单扩展 slot;缺少归档能力时,准备请求返回 `workspace-archive-unsupported`,且不作任何修改。 |
83
83
  | 附件读取 | 对话和快照预览可显示已存储图片;缺少时文本内容仍可阅读。 |
84
84
  | 会话独立日志位置 | 回收站永久删除使用持久化后端公开的 `locate(meta)` 能力。后端不提供会话独立位置时不支持永久删除;失败的条目保留在列表中并显示具体原因。 |
85
- | 公开会话 writer | ZIP 导入、旧版数据恢复 和原件丢失时的快照回退,都通过 Host 公开的 `create` / `append` / `locate` 能力写入;Host 提供专用恢复入口时优先使用。 |
85
+ | 公开会话 writer | ZIP 导入、旧快照恢复和原件丢失时的快照回退,都通过 Host 公开的 `create` / `append` / `locate` 能力写入;Host 提供专用恢复入口时优先使用。 |
86
86
  | 缺少写入能力 | 操作返回 `restore-unsupported`,不会写入或覆盖数据。 |
87
87
 
88
- 降级到不显示旧版数据或不识别回收快照的版本前,请备份 `$DSH_HOME/plugin-data/archived-chats/`。
88
+ 降级到不识别统一回收站或新版快照状态的版本前,请备份 `$DSH_HOME/plugin-data/archived-chats/`。
89
89
 
90
90
  ## 演示预览
91
91
 
@@ -100,12 +100,12 @@ dsh plugin --profile web update dsh-archived-chats
100
100
  <td><img src="assets/screenshots/preview-02.png" alt="全文搜索、筛选、标签与命中摘要"><br><sub>全文搜索</sub></td>
101
101
  </tr>
102
102
  <tr>
103
- <td><img src="assets/screenshots/preview-03.png" alt="带已存储图片的原生只读旧版数据预览"><br><sub>原生只读预览</sub></td>
104
- <td><img src="assets/screenshots/preview-04.png" alt="带恢复和删除操作的旧版数据"><br><sub>旧版数据</sub></td>
103
+ <td><img src="assets/screenshots/preview-03.png" alt="带已存储图片的原生只读快照预览"><br><sub>原生只读预览</sub></td>
104
+ <td><img src="assets/screenshots/preview-04.png" alt="已有快照统一进入回收站"><br><sub>统一回收站</sub></td>
105
105
  </tr>
106
106
  <tr>
107
- <td><img src="assets/screenshots/preview-05.png" alt="清空旧版数据前的不可恢复确认"><br><sub>清空旧版数据确认</sub></td>
108
- <td><img src="assets/screenshots/preview-06.png" alt="保护快照、恢复和永久删除"><br><sub>回收站</sub></td>
107
+ <td><img src="assets/screenshots/preview-05.png" alt="清空回收站前的不可恢复确认"><br><sub>清空回收站确认</sub></td>
108
+ <td><img src="assets/screenshots/preview-06.png" alt="回收站单条与工作区恢复和永久删除操作"><br><sub>回收站操作</sub></td>
109
109
  </tr>
110
110
  <tr>
111
111
  <td><img src="assets/screenshots/preview-07.png" alt="空间分账和保留策略控件"><br><sub>空间与策略</sub></td>
@@ -134,7 +134,7 @@ Session Archive 目前处于积极维护状态。最新 npm 稳定版会接收
134
134
  npm test
135
135
  ```
136
136
 
137
- 测试覆盖 Host 与浏览器行为、导出导入、旧版数据恢复、回收站、保留策略、全文搜索、响应式布局、公开类型、包内容和仓库卫生。测试只使用隔离临时数据,不读取真实会话。
137
+ 测试覆盖 Host 与浏览器行为、导出导入、旧快照恢复、回收站、保留策略、全文搜索、响应式布局、公开类型、包内容和仓库卫生。测试只使用隔离临时数据,不读取真实会话。
138
138
 
139
139
  ## 卸载
140
140
 
@@ -142,7 +142,7 @@ npm test
142
142
  dsh plugin --profile web remove dsh-archived-chats
143
143
  ```
144
144
 
145
- 卸载只移除插件包,不会删除 `$DSH_HOME/plugin-data/archived-chats/` 中的本地数据,也不会触发回收站永久删除。保留的数据包括 `metadata.json`、`trash.json`、`retention.json`、`snapshots/` 目录,以及尚未迁移时可能存在的旧版 `pending-deletions.json`。之后重新安装仍可继续使用这些数据。准备彻底删除该目录前,请先恢复并备份需要的内容;只有确认其中的数据都不再需要时,才手动删除整个目录。
145
+ 卸载只移除插件包,不会删除 `$DSH_HOME/plugin-data/archived-chats/` 中的本地数据,也不会触发回收站永久删除。保留的数据包括 `metadata.json`、`trash.json`、`legacy-recycle.json`、`retention.json`、`snapshots/` 目录,以及尚未迁移时可能存在的旧版 `pending-deletions.json`。之后重新安装仍可继续使用这些数据。准备彻底删除该目录前,请先恢复并备份需要的内容;只有确认其中的数据都不再需要时,才手动删除整个目录。
146
146
 
147
147
  ## 许可证
148
148
 
@@ -10,7 +10,7 @@ The plugin has a Host service half and a browser client half:
10
10
 
11
11
  - The Host service in lib/index.js runs inside the DSH Web host, reads the workspace registry and session persistence, and exposes local HTTP routes.
12
12
  - The browser client in lib/client.js registers the Session Archive settings.section and renders state and actions.
13
- - Pure domain logic lives in lib/export.js, lib/import.js, lib/restore.js, lib/metadata.js, lib/search.js, lib/stats.js, lib/insights.js, lib/retention.js, lib/retention-service.js, lib/lineage.js, and lib/workspace-bulk-archive.js. lib/persistence-compat.js narrows the current Host's handle-based reads to a private read-only view; lib/history.js owns legacy-data inventory and preview authorization; lib/history-restore.js owns single-use restore-as-copy transactions. lib/trash.js owns the recycle catalog, lib/snapshot.js owns verified snapshots, and lib/recycle.js composes recycle lifecycle operations.
13
+ - Pure domain logic lives in lib/export.js, lib/import.js, lib/restore.js, lib/metadata.js, lib/search.js, lib/stats.js, lib/insights.js, lib/retention.js, lib/retention-service.js, lib/auto-retention.js, lib/lineage.js, and lib/workspace-bulk-archive.js. lib/persistence-compat.js narrows the current Host's handle-based reads to a private read-only view; lib/history.js owns legacy-snapshot inventory and preview authorization; lib/history-restore.js owns single-use restore-as-copy transactions; and lib/legacy-recycle.js projects old snapshots into the unified Recycle Bin and persists migration state. lib/trash.js owns the regular recycle catalog, lib/snapshot.js owns verified snapshots, and lib/recycle.js composes the ordinary recycle lifecycle.
14
14
 
15
15
  The browser never reads session files directly. All reads and writes go through Host routes.
16
16
 
@@ -26,6 +26,7 @@ GET /plugins/dsh-archived-chats/workspace-archive/workspaces
26
26
  POST /plugins/dsh-archived-chats/workspace-archive/preview
27
27
  POST /plugins/dsh-archived-chats/workspace-archive/apply
28
28
  POST /plugins/dsh-archived-chats/retention/policy
29
+ POST /plugins/dsh-archived-chats/retention/policy/preview
29
30
  POST /plugins/dsh-archived-chats/retention/preview
30
31
  POST /plugins/dsh-archived-chats/retention/apply
31
32
  GET /plugins/dsh-archived-chats/lineage
@@ -54,9 +55,9 @@ POST /plugins/dsh-archived-chats/delete
54
55
  POST /plugins/dsh-archived-chats/delete-all
55
56
  ~~~
56
57
 
57
- Every mutating route, plus preview, preview/image, search, history/preview, and history/preview/image, requires the `x-dsh-archived-chats: 1` header. `GET /history` returns only bounded safe inventory. Legacy-data images require both the snapshot identity and the complete projected descriptor to match.
58
+ Every mutating route, plus preview, preview/image, search, history/preview, and history/preview/image, requires the `x-dsh-archived-chats: 1` header. `GET /history` returns only bounded safe inventory. Compatibility snapshot images require both the snapshot identity and the complete projected descriptor to match. History routes remain a migration and read-compatibility layer; the browser no longer exposes a standalone History view.
58
59
 
59
- Workspace archive lists only safe workspace summaries. Preview accepts exactly one workspace ID and creates a five-minute, single-use token/nonce for at most 2,000 ordered eligible IDs. Apply accepts only that token and nonce, never caller-selected IDs. Eligibility requires unarchived membership in the existing workspace, no non-idle Host agent, and an inspected live or persisted event log containing `turn/start`; an empty new-session window is classified as `session-empty`, while an inspection failure is `session-unavailable`, and both fail closed. On Hosts without agent status, a loaded session is conservatively considered live. Candidate inspection is bounded to eight concurrent reads. Each item revalidates membership, archive state, agent status, and conversation content inside the shared lifecycle queue, then invokes the public `workspaceRegistry.archiveSession()` receiver-bound to the registry. New chats after preview are excluded; an item that became live, empty, unavailable, archived, or detached is reported and skipped. Successful archives do not capture snapshots. This feature changes neither workspace membership nor a workspace path or directory, and never moves a chat across workspaces. A Host without public `archiveSession` returns `workspace-archive-unsupported` without mutation.
60
+ Workspace archive lists only safe workspace summaries with at least one eligible chat. The browser may select one or more workspaces, but it still calls Preview separately for each workspace. Preview accepts exactly one workspace ID and creates a five-minute, single-use token/nonce for at most 2,000 ordered eligible IDs. The aggregate confirmation retains only nonempty previews; if every workspace became empty, the refreshed chooser returns without an empty-result confirmation. Apply accepts one workspace token and nonce at a time, never caller-selected IDs; the browser consumes those credentials in selection order and combines their safe results. Eligibility requires unarchived membership in the existing workspace, no non-idle Host agent, and an inspected live or persisted event log containing `turn/start`; an empty new-session window is classified as `session-empty`, while an inspection failure is `session-unavailable`, and both fail closed. On Hosts without agent status, a loaded session is conservatively considered live. Candidate inspection is bounded to eight concurrent reads. Each item revalidates membership, archive state, agent status, and conversation content inside the shared lifecycle queue, then invokes the public `workspaceRegistry.archiveSession()` receiver-bound to the registry. New chats after preview are excluded; an item that became live, empty, unavailable, archived, or detached is reported and skipped. Successful archives do not capture snapshots. This feature changes neither workspace membership nor a workspace path or directory, and never moves a chat across workspaces. A Host without public `archiveSession` returns `workspace-archive-unsupported` without mutation.
60
61
 
61
62
  ## State and local data
62
63
 
@@ -65,15 +66,16 @@ The state route joins archived sessions, workspace, tags, notes, and metadataUpd
65
66
  ~~~text
66
67
  $DSH_HOME/plugin-data/archived-chats/metadata.json
67
68
  $DSH_HOME/plugin-data/archived-chats/trash.json
69
+ $DSH_HOME/plugin-data/archived-chats/legacy-recycle.json
68
70
  $DSH_HOME/plugin-data/archived-chats/retention.json
69
71
  $DSH_HOME/plugin-data/archived-chats/snapshots/
70
72
  ~~~
71
73
 
72
- Metadata and recycle catalogs are versioned. Writes serialize and atomically replace their documents through temporary files. An unreadable or unsupported `trash.json` is preserved byte-for-byte, hides no archived sessions, and disables recycle mutations.
74
+ Metadata, recycle catalogs, and the legacy-snapshot migration catalog are versioned. Writes serialize and atomically replace their documents through temporary files. An unreadable or unsupported `trash.json` is preserved byte-for-byte, hides no archived sessions, and disables recycle mutations. An unreadable `legacy-recycle.json` blocks legacy-snapshot migration operations rather than guessing state.
73
75
 
74
76
  The stats route measures session directories with concurrency four, skips symbolic links, and caches results for 30 seconds. A measurement failure marks only that row unavailable; list and mutation actions continue. Delete invalidates the affected cache row.
75
77
 
76
- Insights joins session measurement with a stream-verified snapshot inventory and counts repeated snapshot attachments only from validated SHA-256 descriptors. The browser keeps totals in summary cards and presents session/snapshot inventories only in bounded searchable dialogs. retention.json retains a compatible version-one schema. Only Recycle Bin age contributes cleanup candidates; legacy snapshot-count, age, and quota fields do not schedule snapshot deletion. Saving never runs cleanup. Preview issues a five-minute single-use token/nonce and apply revalidates inside the lifecycle queue; recycle candidates still delegate to recycle purge. Lineage uses only durable parentSession edges, never rewrites headers, and resolves titles for at most 100 untitled active source nodes already included in the focused tree. Its 5,000-node cap bounds the PROJECTED graph, not the Host's store: `focusIds` narrows the output to archived and recycled chats plus their explaining context, so a store of 100,000 sessions with 25 archived projects 25 nodes. Header fields are coerced per node rather than validated whole-graph — an unanticipated `origin` value, an absent `createdAt`, a changed numeric type each degrade that node to a value the public LineageNode already allows, and only an unusable identity drops a row. A projection that rejected the whole graph for one unrecognized header let a Host-side change disable the panel with no plugin change at all; malformed workspace and recycle entries are skipped for the same reason.
78
+ Insights joins session measurement with a stream-verified snapshot inventory and counts repeated snapshot attachments only from validated SHA-256 descriptors. The browser keeps totals in summary cards and presents session/snapshot inventories only in bounded searchable dialogs. retention.json uses schema version 2 with an explicit recycleAutoDelete flag. Version-one documents remain readable, force that flag to false, and are not silently rewritten. Only Recycle Bin age contributes cleanup candidates; legacy snapshot-count, age, and quota fields do not schedule snapshot deletion. Policy preview issues a five-minute single-use token/nonce for enabling or shortening, bound to the current policy, proposed policy, and expired candidates. Policy save accepts { policy, confirmation: { token, nonce } } and revalidates under the lifecycle lock; the save request does not perform deletion itself. Automatic checks begin after startup recovery and repeat once a minute without overlap. Each candidate delegates to unified recycle purge, revalidating the setting and record inside its lifecycle lock before writing purge-pending intent. Plugin disposal stops new jobs; already-committed permanent deletions are retried. Legacy retention/preview and retention/apply manual APIs remain compatible, but the UI no longer has an extra manual cleanup-preview step. Lineage uses only durable parentSession edges, never rewrites headers, and resolves titles for at most 100 untitled active source nodes already included in the focused tree. Its 5,000-node cap bounds the PROJECTED graph, not the Host's store: `focusIds` narrows the output to archived and recycled chats plus their explaining context, so a store of 100,000 sessions with 25 archived projects 25 nodes. Header fields are coerced per node rather than validated whole-graph — an unanticipated `origin` value, an absent `createdAt`, a changed numeric type each degrade that node to a value the public LineageNode already allows, and only an unusable identity drops a row. A projection that rejected the whole graph for one unrecognized header let a Host-side change disable the panel with no plugin change at all; malformed workspace and recycle entries are skipped for the same reason.
77
79
 
78
80
  ## Preview and full-text search
79
81
 
@@ -83,15 +85,15 @@ The preview/image authorization sequence is fixed: first require POST and `x-dsh
83
85
 
84
86
  Cross-session persistence inspection is limited to four concurrent reads, stops scheduling batches once the hit limit is satisfied, and aborts an older browser request when a newer search starts. A broken session is reported in `skipped` while other hits still succeed. Canonical projection limits each segment to 256 Ki code points, each message to 1 Mi code points and 1,000 segments, and each session to 10,000 projected messages; unknown structured values are bounded by depth, node, and character budgets before stringify. A 30-second TTL, 64-session LRU, and per-session cache cap keep bounded projections resident. Unarchive, delete, and restore invalidate affected cache entries.
85
87
 
86
- ## Legacy-data compatibility and recovery
88
+ ## Legacy-snapshot compatibility and unified recycling
87
89
 
88
- Archiving no longer calls `history/capture`; that compatibility route refuses new History captures. Upgrades preserve existing snapshots. Existing inventory, preview, restore, and delete routes serve **Storage & Retention Legacy data**. Recycle Bin operations still capture protection snapshots required for recovery.
90
+ Archiving no longer calls `history/capture`; that compatibility route refuses new History captures. Upgrades preserve existing snapshots. `legacy-recycle.js` projects snapshots not referenced by regular recycle records as `legacy:<snapshotId>` Recycle Bin items, so the browser displays them in the unified Recycle Bin. Existing History inventory, preview, restore, and delete routes remain for migration and compatible callers. Recycle Bin operations still capture protection snapshots required for recovery.
89
91
 
90
92
  `history.js` groups published snapshots as `archived`, `recycled`, or `history-only`, inspects no more than 5,000 snapshot directories, shares one in-flight request, and caches completed inventory for 30 seconds. Inventory contains only safe title/workspace title, timestamps, sizes, attachment counts, and protection state; degraded entries expose only snapshot ID and a stable code. Paginated preview and image reads revalidate snapshot identity, digests, and complete descriptors without returning paths or raw records.
91
93
 
92
- `history-restore.js` fully validates the snapshot, asks the Host for a new session ID, and issues a five-minute single-use token/nonce. Confirmation consumes the credential before writes and rechecks the manifest, then creates persistence, rewrites session/attachment identities, appends events, restores workspace and metadata, and commits archive registry state last. Failures reverse plugin-controlled steps. The source session and snapshot never change, and the plugin makes no claim that Host-global attachment objects were deleted.
94
+ `history-restore.js` fully validates the snapshot, asks the Host for a new session ID, and issues a five-minute single-use token/nonce. Confirmation consumes the credential before writes and rechecks the manifest, then creates persistence, rewrites session/attachment identities, appends events, restores workspace and metadata, and commits archive registry state last. After commit, unified recycling marks the migration state `restore-complete`, then removes the restored snapshot and migration entry; a cleanup failure retains cleanup state without undoing the successfully created archived copy. Failures before commit reverse plugin-controlled steps. The source session and snapshot do not change before commit, and the plugin makes no claim that Host-global attachment objects were deleted.
93
95
 
94
- Legacy-data item deletion and clear-all both enter the shared lifecycle queue and bypass the ordinary 30-second cache/in-flight list so current snapshot and recycle-protection state is recomputed. Both reject a snapshot a recycle record still names, degraded or not that record is its last claim. Both otherwise accept a degraded snapshot: it can no longer be previewed or restored from, but its bytes are still on disk and no other surface can reclaim them (retention now plans only Recycle Bin purges), so refusing here would leak the store permanently. Deletion physically removes the plugin snapshot and its attachment copies, while the original chat and other versions remain unchanged.
96
+ Legacy-snapshot permanent deletion and Empty Recycle Bin both enter the shared lifecycle queue and bypass the ordinary 30-second cache/in-flight list so current snapshot and regular recycle-protection state is recomputed. A snapshot referenced by a regular recycle record is not also projected as a legacy item. A degraded snapshot cannot be previewed or restored, but it can be permanently deleted through the unified Recycle Bin so its bytes remain reclaimable. Deletion physically removes the plugin snapshot and its attachment copies while leaving the source chat and other snapshots unchanged.
95
97
 
96
98
  ## Export flow
97
99
 
@@ -118,19 +120,19 @@ import/inspect accepts only version-one ZIPs produced by this plugin. The Host s
118
120
  5. restore.js uses a feature-detected adapter to write sessions, metadata, and archive state.
119
121
  6. Any failure rolls back staged data and never overwrites an existing session.
120
122
 
121
- The confirmation token expires quickly, can be used once, and is bounded to eight retained plans and 128 MiB total per process. Confirmation-time conflict revalidation, staging, and commit all run inside the shared lifecycle queue. Import resolves a session writer by capability: a dedicated Host restore entry point when one exists, otherwise the ordinary `create` / `append` / `locate` surface — the same capability legacy-data recovery writes through, so import works wherever that works. The append writer carries its own session-scoped rollback (it confirms the located directory is the session's own before creating anything), so a separate removal capability is required only for a dedicated restore entry point. Archive and metadata write capabilities are still required. The staged id does not exist yet, so a session reader that fails closed on unknown ids is the expected answer to the capability probe and never aborts the restore. Workspace attach is used only with a matching detach, otherwise the item restores ungrouped with a warning. A boundary that throws after changing state is compensated in reverse order; failed compensation is reported explicitly rather than returning false success.
123
+ The confirmation token expires quickly, can be used once, and is bounded to eight retained plans and 128 MiB total per process. Confirmation-time conflict revalidation, staging, and commit all run inside the shared lifecycle queue. Import resolves a session writer by capability: a dedicated Host restore entry point when one exists, otherwise the ordinary `create` / `append` / `locate` surface — the same capability legacy-snapshot recovery writes through, so import works wherever that works. The append writer carries its own session-scoped rollback (it confirms the located directory is the session's own before creating anything), so a separate removal capability is required only for a dedicated restore entry point. Archive and metadata write capabilities are still required. The staged id does not exist yet, so a session reader that fails closed on unknown ids is the expected answer to the capability probe and never aborts the restore. Workspace attach is used only with a matching detach, otherwise the item restores ungrouped with a warning. A boundary that throws after changing state is compensated in reverse order; failed compensation is reported explicitly rather than returning false success.
122
124
 
123
125
  ## Recycle and protection-snapshot lifecycle
124
126
 
125
127
  `trash.json` permits only `trashed`, `purge-pending`, and `degraded`. Legal transitions are `missing -> trashed`, `trashed/degraded -> purge-pending`, and removal of an existing state after a committed transaction. A `purge-pending` record cannot restore.
126
128
 
127
- Protection manifests use `dsh-archived-chats/snapshot` v1 and session payloads use `dsh-archived-chats/snapshot-session` v1. Each recycle record names one active snapshot; retained snapshots from restore/recycle cycles remain available under Legacy data until explicit deletion or permanent purge. Exact limits are 4 MiB manifest, 64 MiB session JSON, 1,000 attachments, 32 MiB each, and 512 MiB total. Restore validation streams attachment digests first and rereads one attachment at a time immediately before Host writes, never retaining all attachment bytes together. Snapshot publication/deletion and state-file renames sync file and parent-directory durability, with a safe fallback on Windows filesystems that do not expose directory fsync. Windows cannot atomically replace a file or remove a directory entry while another handle is open on it — an indexer or antivirus scan is enough — so replaces and recursive removals retry the transient `EPERM` / `EACCES` / `EBUSY` codes there, bounded, and only there: on POSIX the same codes are permanent conditions and retrying would only delay the same failure. Snapshot publication also treats a rename refused onto an existing directory as a conflict after probing the destination, because Windows reports that as `EPERM` rather than `EEXIST`. Path containment is tested with the platform separator and rejects an absolute answer, so a Windows `..\` escape or a different drive letter cannot read as inside the root.
129
+ Protection manifests use `dsh-archived-chats/snapshot` v1 and session payloads use `dsh-archived-chats/snapshot-session` v1. Each regular recycle record names one active snapshot. A valid snapshot left without a regular recycle reference after restore or another recycle cycle automatically appears as a legacy-snapshot item in the unified Recycle Bin until it is restored as a copy or permanently deleted. Exact limits are 4 MiB manifest, 64 MiB session JSON, 1,000 attachments, 32 MiB each, and 512 MiB total. Restore validation streams attachment digests first and rereads one attachment at a time immediately before Host writes, never retaining all attachment bytes together. Snapshot publication/deletion and state-file renames sync file and parent-directory durability, with a safe fallback on Windows filesystems that do not expose directory fsync. Windows cannot atomically replace a file or remove a directory entry while another handle is open on it — an indexer or antivirus scan is enough — so replaces and recursive removals retry the transient `EPERM` / `EACCES` / `EBUSY` codes there, bounded, and only there: on POSIX the same codes are permanent conditions and retrying would only delay the same failure. Snapshot publication also treats a rename refused onto an existing directory as a conflict after probing the destination, because Windows reports that as `EPERM` rather than `EEXIST`. Path containment is tested with the platform separator and rejects an absolute answer, so a Windows `..\` escape or a different drive letter cannot read as inside the root.
128
130
 
129
131
  Move ordering is: validate archive ownership → dispose or park a live session → capture and verify snapshot → recheck ownership → atomically commit `trashed` → invalidate caches. Ordinary move never removes the persistence artifact.
130
132
 
131
133
  Archived sessions also expose an explicit direct permanent-delete action. It uses the same session-scoped physical-delete checks and crash-recovery bracket as Recycle Bin purge, but intentionally skips snapshot creation and recycle storage after a separate irreversible confirmation. Ordinary delete remains recoverable by default.
132
134
 
133
- Restore first rejects an existing-ID conflict. With an intact original it restores archive visibility and removes only the recycle record, without rewriting persistence; the snapshot remains available under Legacy data. With a missing original it completes validation and attachment-identity republishing before writing through public `create` / `append` / `saveImage` capabilities. A failure rolls back the new artifact and retains trash.
135
+ Restore first rejects an existing-ID conflict. With an intact original it restores archive visibility and removes only the regular recycle record without rewriting persistence; the protection snapshot then appears as a legacy-snapshot item in the unified Recycle Bin. With a missing original it completes validation and attachment-identity republishing before writing through public `create` / `append` / `saveImage` capabilities. A failure rolls back the new artifact and retains trash.
134
136
 
135
137
  Permanent purge persists `purge-pending` before physical writes, then removes every snapshot for that source, then the original session, and finally the recycle record. The session delete is deliberately last: a failure before it leaves the original intact and the record completable, rather than a `purge-pending` record whose session is already gone and which can therefore neither restore nor complete. The snapshot sweep attributes each published snapshot by manifest identity so a snapshot that fails validation is still removed when it belongs to this session, and an unrelated unverifiable snapshot is skipped instead of aborting the sweep — corruption elsewhere in the store must never make a purge impossible. The recycle record also names its own snapshot id, covering one damaged past attribution. Workspace, metadata, snapshot, or physical-delete failures retain `purge-pending`; snapshot deletion is rescanned before success can be returned. Physical deletion additionally requires the located artifact to sit in a directory named for the session itself, so a backend layout that shares one parent between sessions can never have that parent removed. Startup recovery retries only `purge-pending`, never plain `trashed`. Legacy `pending-deletions.json` is strict read-only migration input: each still-archived ID becomes recoverable trash and is never boot-deleted merely because of the old marker.
136
138
 
@@ -139,12 +141,13 @@ Permanent purge persists `purge-pending` before physical writes, then removes ev
139
141
  client.js registers an order-30 `settings.section` plus a `shell.overlay`, and uses public Host archive services and design tokens. Workspace archive UI state stays inside the plugin-owned settings section and requires no workspace-action slot or shared client store. The page state includes:
140
142
 
141
143
  - A frame-wide archive success notice in `shell.overlay`: during its effect lifetime the plugin wraps public `workspaces.archiveSession` and reports success only after the original succeeds. View and Undo remain available, with a three-second dismissal. No history capture is requested.
142
- - An **Archive workspace chats** action in **Settings → Session Archive**: it opens a workspace chooser, then prepares the selected workspace and shows one confirmation with the exact eligible count, Archived destination, and only a nonzero live-skip count, with no visible session preview. Full success refreshes consumers and closes; any skipped or failed outcome retains the per-item result until dismissed.
144
+ - An **Archive workspace chats** action in **Settings → Session Archive**: the chooser lists only workspaces with eligible chats and supports single selection, multiple selection, and Select all that toggles off on a second click. The bottom-right Confirm button is the only continuation. The client prepares each selection, drops workspaces that concurrently became empty, and shows one aggregate confirmation with the exact total eligible count, Archived destination, and only a nonzero live-skip count, with no visible session preview. Full success refreshes consumers and closes; any skipped or failed outcome retains the per-item result until dismissed.
143
145
  - Archived sessions and workspace groups.
144
146
  - Search, type/project/tag filters, and sorting.
145
147
  - Tag and note editor.
146
- - Selected-item export, unarchive, and move to Recycle Bin.
147
- - Four tabs: Archived, Recycle Bin, Storage & Retention, and Origins & Branches. Storage exposes Legacy data on demand for prior snapshot preview, recovery as a new archived copy, and confirmed deletion. Restore confirmation focuses Cancel first and never places token/nonce in the render tree. Storage and relationship views retain bounded dialogs and read-only relationship projection.
148
+ - Row actions handle one chat in this order: preview, edit tags and note, permanent delete, and Unarchive; there is no single-chat export. Archive workspace menus provide Export all, Unarchive all, Delete all permanently, and Move all to Recycle Bin in that order. Recycle Bin workspace menus provide Restore all and Delete all permanently. Neither view has multi-select state, row checkboxes, or a batch toolbar.
149
+ - Archived and Recycle Bin title rows provide cross-workspace Delete all and Empty Recycle Bin actions respectively; the latter covers every recycled chat and protection snapshot. Recycle Bin row restore and permanent delete are named, tooltip-backed icon buttons.
150
+ - Four tabs: Archived, Recycle Bin, Storage & Retention, and Origins & Branches. Existing snapshots expose preview, recovery as a new archived copy, and confirmed deletion in the Recycle Bin. Restore confirmation focuses Cancel first and never places token/nonce in the render tree. Storage and relationship views retain bounded dialogs and read-only relationship projection.
148
151
  - Import preview, disabled conflicts, and restore results.
149
152
  - Responsive settings-page markers and sidebar refresh injection.
150
153
 
@@ -159,7 +162,7 @@ The browser never mutates files directly. After an operation, the Host response
159
162
  ## Security and failure policy
160
163
 
161
164
  - All state-changing routes require POST and the guard header.
162
- - Legacy-data responses exclude workspace/snapshot/attachment paths, raw events, notes, and confirmation tokens; logs contain only IDs and stable codes.
165
+ - Compatibility History responses and legacy-snapshot Recycle Bin items exclude workspace/snapshot/attachment paths, raw events, notes, and confirmation tokens; logs contain only IDs and stable codes.
163
166
  - Workspace archive responses expose only safe workspace IDs/titles, eligible counts, session IDs/titles/timestamps in a confirmed preview, and stable per-item outcomes; workspace paths, event bodies, notes, attachment paths, and confirmation credentials stay out of logs and rendered results.
164
167
  - Import limits ZIP size, entries, paths, versions, and JSON structure, rejecting traversal, duplicates, and prototype-pollution keys.
165
168
  - Ordinary delete never invokes physical purge; only a committed recycle record can enter purge.
@@ -173,7 +176,7 @@ The plugin adapts through Host capability detection: archive reads, attachment r
173
176
 
174
177
  Snapshot and ZIP schema v1 cannot preserve `inheritedEventCount`. When a current read handle reports an inherited prefix greater than zero, the adapter returns `session-inspection-unsupported` before reading events and still closes the handle, avoiding a silently flattened branch history. Workspace archive no longer depends on snapshot capture. Recycle protection and ZIP export continue to reject unsupported inherited event data. This UI change does not widen the snapshot or restore protocol.
175
178
 
176
- On Hosts with the compatible legacy writer surface, Import, legacy-data recovery, and snapshot fallback when the original is missing still write through the public `create` / `append` / `locate` capability, or a dedicated restore entry point where the Host offers one; only a Host exposing neither returns `restore-unsupported` without mutation. A capability set that no shipped Host satisfies is not an acceptable guard — it makes the feature permanently dead rather than gracefully degraded. Back up the complete plugin-data directory before downgrading to a release that does not display legacy data or understand recycle snapshots.
179
+ On Hosts with the compatible legacy writer surface, Import, legacy-snapshot recovery, and snapshot fallback when the original is missing still write through the public `create` / `append` / `locate` capability, or a dedicated restore entry point where the Host offers one; only a Host exposing neither returns `restore-unsupported` without mutation. A capability set that no shipped Host satisfies is not an acceptable guard — it makes the feature permanently dead rather than gracefully degraded. Back up the complete plugin-data directory before downgrading to a release that does not understand the unified Recycle Bin or newer snapshot state.
177
180
 
178
181
  Coverage includes:
179
182
 
@@ -184,8 +187,8 @@ Coverage includes:
184
187
  - stats.js symlink handling, caching, and concurrency limits.
185
188
  - search.js message projection, Unicode search, pagination, partial failures, and TTL/LRU caching.
186
189
  - trash.js, snapshot.js, and recycle.js format validation, concurrency, recovery, rollback, crash intent, and legacy migration.
187
- - insights.js, retention.js, retention-service.js, and lineage.js trusted accounting, policy bounds, short-lived authority, revalidation, and bounded graph projection.
188
- - history.js and history-restore.js legacy-data preservation, cache invalidation, snapshot authorization, single-use confirmation, transaction rollback, and source immutability.
190
+ - insights.js, retention.js, retention-service.js, auto-retention.js, and lineage.js trusted accounting, policy bounds, short-lived authority, revalidation, scheduler lifecycle, and bounded graph projection.
191
+ - history.js, history-restore.js, and legacy-recycle.js legacy-snapshot preservation, unified recycle projection, cache invalidation, snapshot authorization, single-use confirmation, transaction rollback, and source immutability.
189
192
  - Host routes and browser settings smoke/responsive behavior.
190
193
 
191
194
  Run:
@@ -10,7 +10,7 @@
10
10
 
11
11
  - Host 服务层位于 lib/index.js,运行在 DSH Web 宿主中,读取工作区注册表和会话持久层,并提供本地 HTTP 路由。
12
12
  - 浏览器客户端位于 lib/client.js,通过 settings.section 注册「会话档案」设置页,负责展示状态和发起操作。
13
- - 纯领域逻辑拆分在 lib/export.js、lib/import.js、lib/restore.js、lib/metadata.js、lib/search.js、lib/stats.js、lib/insights.js、lib/retention.js、lib/retention-service.js、lib/lineage.js 和 lib/workspace-bulk-archive.js 中。lib/persistence-compat.js 将新版 Host 的句柄读取面收敛为插件内部只读视图;lib/history.js 负责旧版数据安全清单和预览授权,lib/history-restore.js 负责单次确认的恢复为副本事务。lib/trash.js 负责版本化回收目录,lib/snapshot.js 负责可验证快照,lib/recycle.js 组合回收生命周期。
13
+ - 纯领域逻辑拆分在 lib/export.js、lib/import.js、lib/restore.js、lib/metadata.js、lib/search.js、lib/stats.js、lib/insights.js、lib/retention.js、lib/retention-service.js、lib/auto-retention.js、lib/lineage.js 和 lib/workspace-bulk-archive.js 中。lib/persistence-compat.js 将新版 Host 的句柄读取面收敛为插件内部只读视图;lib/history.js 负责旧快照安全清单和预览授权,lib/history-restore.js 负责单次确认的恢复为副本事务,lib/legacy-recycle.js 将旧快照投影进统一回收站并保存迁移状态。lib/trash.js 负责版本化回收目录,lib/snapshot.js 负责可验证快照,lib/recycle.js 组合普通回收生命周期。
14
14
 
15
15
  浏览器不直接访问会话文件。所有读取和写入都经 Host 路由完成。
16
16
 
@@ -26,6 +26,7 @@ GET /plugins/dsh-archived-chats/workspace-archive/workspaces
26
26
  POST /plugins/dsh-archived-chats/workspace-archive/preview
27
27
  POST /plugins/dsh-archived-chats/workspace-archive/apply
28
28
  POST /plugins/dsh-archived-chats/retention/policy
29
+ POST /plugins/dsh-archived-chats/retention/policy/preview
29
30
  POST /plugins/dsh-archived-chats/retention/preview
30
31
  POST /plugins/dsh-archived-chats/retention/apply
31
32
  GET /plugins/dsh-archived-chats/lineage
@@ -54,9 +55,9 @@ POST /plugins/dsh-archived-chats/delete
54
55
  POST /plugins/dsh-archived-chats/delete-all
55
56
  ~~~
56
57
 
57
- 所有修改路由以及会返回对话内容的 preview、preview/image、search、history/preview 和 history/preview/image 路由都要求 `x-dsh-archived-chats: 1` 请求头。`GET /history` 只返回有界安全清单;旧版数据图片只在快照身份与完整描述符同时匹配时返回。
58
+ 所有修改路由以及会返回对话内容的 preview、preview/image、search、history/preview 和 history/preview/image 路由都要求 `x-dsh-archived-chats: 1` 请求头。`GET /history` 只返回有界安全清单;兼容快照图片只在快照身份与完整描述符同时匹配时返回。History 路由保留为迁移和只读兼容层,浏览器不再显示独立 History 页面。
58
59
 
59
- 工作区归档只列出安全的工作区摘要。preview 只接受一个工作区 ID,并为最多 2,000 条符合条件且有序的会话 ID 签发 5 分钟有效、只能使用一次的 token/nonceapply 只接受该 token 和 nonce,绝不接受调用方指定的会话 ID。符合条件要求会话仍属于现有工作区、尚未归档、没有非空闲 Host agent,并且检查到活动或持久化事件日志包含 `turn/start`;空白的新会话窗口标记为 `session-empty`,检查失败标记为 `session-unavailable`,两者都会保守跳过。Host 不提供 agent 状态时,已加载会话会被保守视为活动;候选内容检查最多并发读取 8 条。每一项都会在共享生命周期队列内重新检查归属、归档状态、agent 状态和对话内容,然后以 registry 为 receiver 调用公开的 `workspaceRegistry.archiveSession()`。预览后新增的聊天不会纳入;已变为运行中、空白、不可确认、已归档或脱离工作区的项目会明确跳过。成功归档不再抓取快照。此功能不改变工作区成员关系、路径或目录,也不会在工作区之间移动聊天。Host 没有公开 `archiveSession` 时返回 `workspace-archive-unsupported`,且不作修改。
60
+ 工作区归档只列出安全且至少有一条符合条件会话的工作区摘要。浏览器可选择一个或多个工作区,但仍为每个工作区分别调用 preview;preview 只接受一个工作区 ID,并为最多 2,000 条符合条件且有序的会话 ID 签发 5 分钟有效、只能使用一次的 token/nonce。汇总确认只保留具有非空预览的工作区;如果全部变为空,返回刷新后的选择器,不进入空结果确认。apply 每次只接受一个工作区的 token 和 nonce,绝不接受调用方指定的会话 ID;浏览器按选择顺序执行各凭据并汇总安全结果。符合条件要求会话仍属于现有工作区、尚未归档、没有非空闲 Host agent,并且检查到活动或持久化事件日志包含 `turn/start`;空白的新会话窗口标记为 `session-empty`,检查失败标记为 `session-unavailable`,两者都会保守跳过。Host 不提供 agent 状态时,已加载会话会被保守视为活动;候选内容检查最多并发读取 8 条。每一项都会在共享生命周期队列内重新检查归属、归档状态、agent 状态和对话内容,然后以 registry 为 receiver 调用公开的 `workspaceRegistry.archiveSession()`。预览后新增的聊天不会纳入;已变为运行中、空白、不可确认、已归档或脱离工作区的项目会明确跳过。成功归档不再抓取快照。此功能不改变工作区成员关系、路径或目录,也不会在工作区之间移动聊天。Host 没有公开 `archiveSession` 时返回 `workspace-archive-unsupported`,且不作修改。
60
61
 
61
62
  ## 状态和本地数据
62
63
 
@@ -65,15 +66,20 @@ state 路由把归档会话、工作区、标签、备注和 metadataUpdatedAt
65
66
  ~~~text
66
67
  $DSH_HOME/plugin-data/archived-chats/metadata.json
67
68
  $DSH_HOME/plugin-data/archived-chats/trash.json
69
+ $DSH_HOME/plugin-data/archived-chats/legacy-recycle.json
68
70
  $DSH_HOME/plugin-data/archived-chats/retention.json
69
71
  $DSH_HOME/plugin-data/archived-chats/snapshots/
70
72
  ~~~
71
73
 
72
- 元数据和回收目录均带版本号,写入通过队列串行化并用临时文件原子替换。无法解析或不支持的 `trash.json` 保留原始字节、不隐藏任何归档会话,并禁用回收修改。
74
+ 元数据、回收目录和旧快照迁移目录均带版本号,写入通过队列串行化并用临时文件原子替换。无法解析或不支持的 `trash.json` 保留原始字节、不隐藏任何归档会话,并禁用回收修改;无法读取 `legacy-recycle.json` 时拒绝旧快照迁移操作,不猜测状态。
73
75
 
74
76
  stats 路由以并发 4 测量会话目录,跳过符号链接,结果缓存 30 秒。测量失败只标记当前行不可用,不阻塞列表和其他操作;删除会使对应缓存失效。
75
77
 
76
- insights 将会话目录测量与流式校验的快照清单分账,重复附件只按快照内已验证 SHA-256 统计;浏览器只在摘要卡片中显示总量,会话目录和快照明细通过有界、可搜索弹窗按需呈现。retention.json 保持兼容的 version 1 schema;只有回收站保留天数参与清理规划,旧版快照数量、年龄与容量字段不再规划快照删除;保存策略不执行清理。preview 生成五分钟、单次使用的 token/nonce,apply 在生命周期队列内重检候选,回收站候选仍委托 recycle purge。lineage 只用持久化 parentSession 建树,不修改会话头;对已经进入聚焦关系树但没有安全标题的活动来源节点,最多按需读取 100 个标题事件。其 5,000 节点上限约束的是**实际投影出的**关系图,而不是 Host 的会话库:`focusIds` 已把输出收窄为归档与回收聊天加上解释它们所需的上下文,因此 100,000 个会话、25 条归档只投影 25 个节点。会话头字段按节点逐项收敛而不是整图校验——未预期的 `origin` 取值、缺失的 `createdAt`、变化的数值类型,各自只把该节点降级为公开 LineageNode 已允许的取值,只有身份不可用才丢弃该行。为一个不认识的会话头拒绝整张图,会让 Host 侧的变化在插件毫无改动的情况下直接停用该面板;畸形的工作区与回收记录出于同样理由被跳过。
78
+ insights 将会话目录测量与流式校验的快照清单分账,重复附件只按快照内已验证 SHA-256 统计;浏览器只在摘要卡片中显示总量,会话目录和快照明细通过有界、可搜索弹窗按需呈现。
79
+
80
+ retention.json 使用 version 2 schema,并加入显式 `recycleAutoDelete` 开关;version 1 文件继续兼容读取,其开关被强制视为 false 且不会静默改写磁盘。只有回收站保留天数参与清理规划,旧版快照数量、年龄与容量字段不再规划快照删除。policy/preview 为开启或缩短期限生成五分钟单次 token/nonce,绑定当前策略、拟保存策略与到期候选;policy 保存支持 `{ policy, confirmation: { token, nonce } }`,在生命周期锁内复核并保存,保存请求本身不执行删除。自动任务在启动恢复完成后运行,此后每分钟串行检查;每条候选仍委托统一 recycle purge,在其生命周期锁内重新核对设置与记录后才写入 purge-pending 意图。停用插件会停止新任务;已有永久删除意图继续重试。旧版 retention/preview 与 retention/apply 手动 API 保持兼容,但界面不再提供额外预览清理步骤。
81
+
82
+ lineage 只用持久化 parentSession 建树,不修改会话头;对已经进入聚焦关系树但没有安全标题的活动来源节点,最多按需读取 100 个标题事件。其 5,000 节点上限约束的是**实际投影出的**关系图,而不是 Host 的会话库:`focusIds` 已把输出收窄为归档与回收聊天加上解释它们所需的上下文,因此 100,000 个会话、25 条归档只投影 25 个节点。会话头字段按节点逐项收敛而不是整图校验——未预期的 `origin` 取值、缺失的 `createdAt`、变化的数值类型,各自只把该节点降级为公开 LineageNode 已允许的取值,只有身份不可用才丢弃该行。为一个不认识的会话头拒绝整张图,会让 Host 侧的变化在插件毫无改动的情况下直接停用该面板;畸形的工作区与回收记录出于同样理由被跳过。
77
83
 
78
84
  ## 预览和全文搜索
79
85
 
@@ -83,15 +89,15 @@ preview/image 的授权顺序固定为:先验证 POST 和 `x-dsh-archived-chat
83
89
 
84
90
  跨会话搜索的持久层读取并发上限为 4,达到命中上限后不再调度后续批次,浏览器的新搜索会中止旧请求;单个会话失败会记入 skipped,其他命中仍正常返回。规范投影使用 30 秒 TTL、64 会话 LRU,并限制单段 256 Ki Unicode 码点、单消息 1 Mi 码点/1,000 段、单会话 10,000 条投影消息;结构化未知值在 stringify 前即受深度、节点和字符预算约束。超出投影边界的内容会安全截断且不会常驻缓存。取消归档、删除和恢复会使相关缓存失效。
85
91
 
86
- ## 旧版数据兼容与恢复
92
+ ## 旧快照兼容与统一回收站
87
93
 
88
- 归档不再调用 `history/capture`,该兼容路由拒绝新建历史版本。旧版快照在升级时保留,现有读取、预览、恢复和删除路由供「空间与策略 旧版数据」使用。回收站仍可创建恢复所需的保护快照。
94
+ 归档不再调用 `history/capture`,该兼容路由拒绝新建历史版本。升级时保留已有快照;`legacy-recycle.js` 将未被普通回收记录引用的快照投影为 `legacy:<snapshotId>` 回收条目,浏览器直接在统一回收站中展示。History 清单、预览、恢复和删除路由继续服务迁移与兼容调用方;回收站仍可创建恢复所需的保护快照。
89
95
 
90
96
  `history.js` 将已发布快照分组为 `archived` / `recycled` / `history-only`,单次最多检查 5,000 个快照目录,共用进行中请求并缓存已完成结果 30 秒。清单只含安全标题/工作区标题、时间、大小、附件数和保护状态;降级项只显示快照 ID 与稳定代码。分页预览与图片读取每次都重新验证快照、摘要和完整描述符,不返回路径或原始记录。
91
97
 
92
- `history-restore.js` 先完整验证快照,用 Host 生成新会话 ID,再签发五分钟、单次使用的 token/nonce。确认时先消费凭据并重验 manifest;然后依次创建持久会话、重写会话/附件身份、附加事件、恢复工作区和元数据,最后才写入归档注册表。任一插件控制的边界失败都按逆序回滚;来源会话与快照始终不变,也不声称删除了 Host 全局附件对象。
98
+ `history-restore.js` 先完整验证快照,用 Host 生成新会话 ID,再签发五分钟、单次使用的 token/nonce。确认时先消费凭据并重验 manifest;然后依次创建持久会话、重写会话/附件身份、附加事件、恢复工作区和元数据,最后才写入归档注册表。统一回收服务在提交后将迁移状态标记为 `restore-complete`,再删除已恢复快照和迁移记录;清理失败只保留待清理状态,不会撤销已经成功创建的归档副本。任一插件控制的恢复边界失败都按逆序回滚;来源会话与快照在提交前始终不变,也不声称删除了 Host 全局附件对象。
93
99
 
94
- 旧版数据入口的单条删除与清空操作都进入共用生命周期队列,并绕过普通 30 秒缓存/进行中请求,重新计算当前快照与回收保护关系。两者都拒绝仍被回收记录引用的快照(无论是否降级)——那条记录是它最后的归属。除此之外两者都接受降级快照:它已无法预览或恢复,但字节仍在磁盘上,而其他任何入口都无法回收它们(保留策略现只规划回收站永久删除),在这里拒绝就意味着永久泄漏。删除会物理移除插件快照及其附件副本,但不修改原聊天或其他版本。
100
+ 旧快照永久删除与清空回收站都进入共用生命周期队列,并绕过普通 30 秒缓存/进行中请求,重新计算当前快照与普通回收保护关系。仍被普通回收记录引用的快照不会重复投影为旧快照条目。降级快照无法预览或恢复,但可通过统一回收站永久删除,避免无法回收磁盘字节。删除会物理移除插件快照及其附件副本,但不修改来源聊天或其他快照。
95
101
 
96
102
  ## 导出流程
97
103
 
@@ -118,17 +124,17 @@ import/inspect 只接受本插件版本一导出的 ZIP。Host 以有界压缩
118
124
  5. restore.js 通过能力探测的适配器写入会话、元数据和归档状态。
119
125
  6. 任一步骤失败都回滚暂存数据,不覆盖已有会话。
120
126
 
121
- 确认令牌短期有效且只能使用一次,并受 8 条、总计 128 MiB 的进程内保留上限约束。确认后的冲突重检、暂存和提交全部进入共享生命周期队列。导入按能力解析会话 writer:有专用恢复入口时优先使用,否则使用普通的 `create` / `append` / `locate` 面——也就是旧版数据恢复写入所用的同一能力,因此后者能工作的地方导入就能工作。append writer 自带会话作用域回滚(创建任何东西之前先确认定位到的目录属于该会话本身),所以只有专用恢复入口才需要额外的删除能力;归档与元数据写入能力仍然必需。待恢复 id 尚不存在,因此对未知会话 fail-closed 的读取器正是能力探测的预期回答,不会中断恢复。工作区 attach 必须有对应 detach,否则按未分组警告处理。任一边界即使在抛错前已经改变状态,也会按逆序补偿;无法完成补偿时明确返回 rollback failed,而不会报告恢复成功。
127
+ 确认令牌短期有效且只能使用一次,并受 8 条、总计 128 MiB 的进程内保留上限约束。确认后的冲突重检、暂存和提交全部进入共享生命周期队列。导入按能力解析会话 writer:有专用恢复入口时优先使用,否则使用普通的 `create` / `append` / `locate` 面——也就是旧快照恢复写入所用的同一能力,因此后者能工作的地方导入就能工作。append writer 自带会话作用域回滚(创建任何东西之前先确认定位到的目录属于该会话本身),所以只有专用恢复入口才需要额外的删除能力;归档与元数据写入能力仍然必需。待恢复 id 尚不存在,因此对未知会话 fail-closed 的读取器正是能力探测的预期回答,不会中断恢复。工作区 attach 必须有对应 detach,否则按未分组警告处理。任一边界即使在抛错前已经改变状态,也会按逆序补偿;无法完成补偿时明确返回 rollback failed,而不会报告恢复成功。
122
128
 
123
129
  ## 回收与保护快照生命周期
124
130
 
125
131
  `trash.json` 的合法状态只有 `trashed`、`purge-pending`、`degraded`。合法转换为 `missing -> trashed`、`trashed/degraded -> purge-pending`,以及任一现有状态在事务成功后移除。`purge-pending` 不得恢复。
126
132
 
127
- 保护快照格式是 `dsh-archived-chats/snapshot` v1,会话载荷是 `dsh-archived-chats/snapshot-session` v1。每个回收记录只引用一个活跃快照,重复恢复/回收保留的有效快照可在旧版数据入口访问,直到明确删除或永久删除来源会话。精确上限为:manifest 4 MiB、session JSON 64 MiB、1,000 个附件、单附件 32 MiB、总计 512 MiB;恢复验证先流式校验附件摘要,再在写入 Host 前逐件复读,绝不同时保留全部附件字节。发布、删除与状态文件的 rename 会同步文件和父目录;不支持目录 fsync 的 Windows 文件系统安全降级。Windows 在目标仍被其他句柄打开时无法原子替换文件或移除目录项(索引服务或杀毒扫描即可导致),因此替换与递归删除会在该平台上、且仅在该平台上,对瞬时的 `EPERM` / `EACCES` / `EBUSY` 做有界重试:POSIX 上这些码表示永久条件,重试只会延后同一个失败。快照发布还会在 rename 被拒时探测目标目录再判定为冲突,因为 Windows 把「rename 到已存在目录」报成 `EPERM` 而不是 `EEXIST`。路径包含判定使用平台分隔符并拒绝绝对结果,因此 Windows 的 `..\` 逃逸或不同盘符都不会被误判为在根目录内。
133
+ 保护快照格式是 `dsh-archived-chats/snapshot` v1,会话载荷是 `dsh-archived-chats/snapshot-session` v1。每个普通回收记录只引用一个活跃快照;恢复或重复回收后不再被普通记录引用的有效快照会自动显示为统一回收站中的旧快照条目,直到恢复为副本或永久删除。精确上限为:manifest 4 MiB、session JSON 64 MiB、1,000 个附件、单附件 32 MiB、总计 512 MiB;恢复验证先流式校验附件摘要,再在写入 Host 前逐件复读,绝不同时保留全部附件字节。发布、删除与状态文件的 rename 会同步文件和父目录;不支持目录 fsync 的 Windows 文件系统安全降级。Windows 在目标仍被其他句柄打开时无法原子替换文件或移除目录项(索引服务或杀毒扫描即可导致),因此替换与递归删除会在该平台上、且仅在该平台上,对瞬时的 `EPERM` / `EACCES` / `EBUSY` 做有界重试:POSIX 上这些码表示永久条件,重试只会延后同一个失败。快照发布还会在 rename 被拒时探测目标目录再判定为冲突,因为 Windows 把「rename 到已存在目录」报成 `EPERM` 而不是 `EEXIST`。路径包含判定使用平台分隔符并拒绝绝对结果,因此 Windows 的 `..\` 逃逸或不同盘符都不会被误判为在根目录内。
128
134
 
129
135
  移入顺序为:校验归档所有权 → 处置/停放运行中会话 → 捕获并验证快照 → 再次校验所有权 → 原子写入 `trashed` 记录 → 使缓存失效。普通移入不删除持久层文件。
130
136
 
131
- 恢复先检查同 ID 冲突。原会话完好时只恢复归档可见性并移除回收记录,不重写持久层,保护快照保留在旧版数据入口;原件丢失时先完成所有校验和附件身份重发,然后仅通过公开 `create` / `append` / `saveImage` 能力写入。失败会回滚新建件并保留回收记录。
137
+ 恢复先检查同 ID 冲突。原会话完好时只恢复归档可见性并移除普通回收记录,不重写持久层;随后失去普通回收引用的保护快照会作为旧快照条目显示在统一回收站。原件丢失时先完成所有校验和附件身份重发,然后仅通过公开 `create` / `append` / `saveImage` 能力写入。失败会回滚新建件并保留回收记录。
132
138
 
133
139
  永久删除在任何物理写入前持久化 `purge-pending`,接着删除该来源的全部快照,然后删除原会话,最后移除回收记录。会话删除刻意放在最后:在它之前失败会保留完好的原件和可继续完成的记录,而不是留下一条会话已消失、既不能恢复也不能完成的 `purge-pending`。快照清扫按 manifest 身份归属每个已发布快照,因此校验失败但确属该会话的快照仍会被删除,而无关的无法校验快照会被跳过而不是中断整个清扫——快照库别处的损坏绝不能让永久删除变成不可能。回收记录还会点名自己的快照 id,覆盖损坏到无法归属的那一个。工作区、元数据、快照或物理删除任一步骤失败时都保留 `purge-pending`;快照删除后还会重新扫描确认,不会虚假报告成功。物理删除另外要求定位到的文件位于以该会话自身命名的目录中,因此当后端布局让多个会话共享同一父目录时,那个父目录永远不会被删除。启动恢复仅重试 `purge-pending`,从不删除普通 `trashed`。旧 `pending-deletions.json` 是严格、只读的迁移输入:每个仍归档的 ID 都转成可恢复回收记录,绝不因旧标记在启动时直接删除。
134
140
 
@@ -137,12 +143,13 @@ import/inspect 只接受本插件版本一导出的 ZIP。Host 以有界压缩
137
143
  client.js 注册 order 30 的 `settings.section` 与 `shell.overlay`,并使用 Host 公开的归档服务和设计令牌。工作区归档 UI 状态保存在插件自己的设置区内,不依赖工作区操作 slot 或共享客户端 store。页面状态包括:
138
144
 
139
145
  - `shell.overlay` 中的归档成功提示:插件在 effect 生命周期内包装公开的 `workspaces.archiveSession`,只在原调用成功后显示提示,提供查看与撤销,3 秒后关闭,不请求历史抓取。
140
- - **设置 → 会话档案** 中的 **批量归档工作区** 操作:先打开工作区选择器,再准备所选工作区并显示一次包含精确符合条件数量、已归档去向和仅在非零时出现的活动会话跳过数量的确认,不显示会话预览。全部成功后刷新消费者并关闭;存在跳过或失败时保留逐项结果直至关闭。
146
+ - **设置 → 会话档案** 中的 **批量归档工作区** 操作:选择器只显示有可归档会话的工作区,支持单选、多选和再次点击可取消的全选,右下角“确定”是唯一继续入口。客户端分别准备所选工作区,跳过并发变空的工作区,再显示一次包含精确总数量、已归档去向和仅在非零时出现的活动会话跳过数量的汇总确认,不显示会话预览。全部成功后刷新消费者并关闭;存在跳过或失败时保留逐项结果直至关闭。
141
147
  - 归档列表和工作区分组。
142
148
  - 搜索、类型/项目/标签筛选和排序。
143
149
  - 标签备注编辑器。
144
- - 选中项批量导出、取消归档和移入回收站。
145
- - 已归档、回收站、空间与策略、来源与分支四标签。空间与策略按需提供旧版数据预览、恢复为新的归档副本和确认删除;恢复确认的初始焦点位于取消,token/nonce 不进入渲染树。空间与关系视图保留有界弹窗和只读关系投影。
150
+ - 行级操作负责单条聊天,依次为预览、编辑标签备注、永久删除和取消归档,不提供单条导出;归档工作区菜单依次提供全部导出、全部取消归档、全部永久删除和全部移至回收站;回收站工作区菜单提供全部恢复和全部永久删除。两页均无多选状态、行选择框或批量工具栏。
151
+ - 已归档和回收站标题行分别提供跨工作区的全部删除与清空回收站;后者覆盖所有回收会话与保护快照。回收站行级恢复和永久删除使用具备名称与提示的图标按钮。
152
+ - 已归档、回收站、空间与策略、来源与分支四标签。旧快照在回收站中提供预览、恢复为新的归档副本和确认删除;恢复确认的初始焦点位于取消,token/nonce 不进入渲染树。空间与关系视图保留有界弹窗和只读关系投影。
146
153
  - 导入预览、冲突禁用和恢复结果。
147
154
  - 响应式设置页标记和侧边栏刷新注入面。
148
155
 
@@ -157,7 +164,7 @@ client.js 注册 order 30 的 `settings.section` 与 `shell.overlay`,并使用
157
164
  ## 安全和失败策略
158
165
 
159
166
  - 所有状态变更路由都要求 POST 和 guard header。
160
- - 旧版数据响应不包含工作区/快照/附件路径、原始事件、备注或确认 token;日志只记 ID 和稳定代码。
167
+ - 兼容 History 响应与统一回收站中的旧快照条目不包含工作区/快照/附件路径、原始事件、备注或确认 token;日志只记 ID 和稳定代码。
161
168
  - 工作区归档响应只公开安全的工作区 ID/标题、符合条件数量、已确认预览中的会话 ID/标题/时间和稳定的逐项结果;工作区路径、事件正文、备注、附件路径与确认凭据不会进入日志或渲染结果。
162
169
  - 导入限制 ZIP 大小、条目数量、路径格式、版本和 JSON 结构,拒绝遍历、重复和原型污染字段。
163
170
  - 普通删除从不调用物理清除;仅已提交回收记录可进入 purge。归档列表还提供单独确认的直接永久删除操作,跳过快照和回收站,但复用同一套会话目录边界、崩溃恢复和失败保护;普通删除仍默认可恢复。
@@ -171,7 +178,7 @@ client.js 注册 order 30 的 `settings.section` 与 `shell.overlay`,并使用
171
178
 
172
179
  当前 v1 快照和 ZIP schema 不能保存 `inheritedEventCount`。新版读取句柄报告大于 0 的继承前缀时,适配器会在读取事件前以 `session-inspection-unsupported` 拒绝并关闭句柄,避免把分支历史静默展平。工作区归档不再依赖快照抓取;回收保护与 ZIP 导出仍会拒绝不支持的继承事件数据。这个 UI 变更不扩展快照/恢复协议。
173
180
 
174
- 兼容旧写入面的 Host 仍可让导入、旧版数据恢复和原件丢失时的快照回退通过公开的 `create` / `append` / `locate` 能力写入,Host 提供专用恢复入口时优先使用;只有两者都不存在才返回 `restore-unsupported` 且不写入数据。要求一组没有任何已发布 Host 能满足的能力不是合格的守卫——那会让功能永久失效,而不是优雅降级。旧版若不显示旧版数据或不识别回收快照,降级前应备份整个插件数据目录。
181
+ 兼容旧写入面的 Host 仍可让导入、旧快照恢复和原件丢失时的快照回退通过公开的 `create` / `append` / `locate` 能力写入,Host 提供专用恢复入口时优先使用;只有两者都不存在才返回 `restore-unsupported` 且不写入数据。要求一组没有任何已发布 Host 能满足的能力不是合格的守卫——那会让功能永久失效,而不是优雅降级。降级到不识别统一回收站或新版快照状态的版本前,应备份整个插件数据目录。
175
182
 
176
183
  测试覆盖:
177
184
 
@@ -182,8 +189,8 @@ client.js 注册 order 30 的 `settings.section` 与 `shell.overlay`,并使用
182
189
  - stats.js 的符号链接、缓存和并发限制。
183
190
  - search.js 的消息投影、Unicode 搜索、分页、部分失败与 TTL/LRU 缓存。
184
191
  - trash.js、snapshot.js 和 recycle.js 的格式验证、并发、恢复、回滚、崩溃意图和旧标记迁移。
185
- - insights.js、retention.js、retention-service.js 和 lineage.js 的可信分账、策略边界、短效授权、重检和有界图投影。
186
- - history.js 和 history-restore.js 的旧版数据保留、缓存失效、快照授权、单次确认、事务回滚与来源不变式。
192
+ - insights.js、retention.js、retention-service.js、auto-retention.js 和 lineage.js 的可信分账、策略边界、短效授权、重检、自动任务生命周期和有界图投影。
193
+ - history.js、history-restore.jslegacy-recycle.js 的旧快照保留、统一回收投影、缓存失效、快照授权、单次确认、事务回滚与来源不变式。
187
194
  - Host 路由和浏览器设置页的冒烟及响应式行为。
188
195
 
189
196
  运行: