@michengai/dsh-archive-manager 0.1.39 → 0.1.41

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,17 @@
4
4
 
5
5
  Published release notes are retained below; new versions are added without removing earlier entries.
6
6
 
7
+ ## 0.1.41 - 2026-09-14
8
+
9
+ - Improve the archived-session bulk action bar with grouped selection details and actions, fixing wrapped button labels in narrow content areas.
10
+ - Simplify the bar when nothing is selected and show cross-filter selection counts and scope separately so hidden selections remain clear.
11
+
12
+ ## 0.1.40 - 2026-09-13
13
+
14
+ - Open archived sessions in the native DSH conversation view and continue chatting while keeping them archived, or restore and open them.
15
+ - Close the settings overlay after an archived conversation opens successfully; keep settings open and show an error on failure, with a restore-and-open alternative when host navigation adaptation is unavailable.
16
+ - Localize archive navigation errors and avoid unnecessary warnings when older hosts load or open archives successfully.
17
+
7
18
  ## 0.1.39 - 2026-09-11
8
19
 
9
20
  - Preserve the Codex UI workspace list, styling, and dedicated interactions when both plugins are installed, while keeping archive management available.
@@ -4,6 +4,17 @@
4
4
 
5
5
  以下发布说明会持续保留;新增版本时不再删除较早记录。
6
6
 
7
+ ## 0.1.41 - 2026-09-14
8
+
9
+ - 优化归档会话的批量操作栏:选择摘要与操作分组显示,修复窄内容区内按钮文字折行的问题。
10
+ - 未选择时简化操作栏;跨筛选选择的数量和影响范围独立显示,避免遗漏不可见的已选会话。
11
+
12
+ ## 0.1.40 - 2026-09-13
13
+
14
+ - 支持直接打开归档会话,在 DSH 原生页面查看完整对话并保持归档继续聊天,也可恢复后打开。
15
+ - 修复查看归档后设置遮罩未关闭的问题:打开成功后关闭设置,失败时保留设置并显示错误;宿主导航适配不可用时可恢复后打开。
16
+ - 归档打开失败提示随界面语言显示,旧宿主正常加载和打开归档时不再产生多余警告。
17
+
7
18
  ## 0.1.39 - 2026-09-11
8
19
 
9
20
  - 修复与 Codex UI 共存时抢占其工作区列表的问题,保留 Codex UI 的列表样式和专属交互,同时提供归档管理入口。
package/README.md CHANGED
@@ -14,196 +14,150 @@
14
14
  [![npm package](https://img.shields.io/npm/v/%40michengai%2Fdsh-archive-manager.svg?label=npm%20package)](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
15
15
  [![npm downloads](https://img.shields.io/npm/dt/%40michengai%2Fdsh-archive-manager.svg?label=npm%20downloads)](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
16
16
  [![DSH Web Plugin](https://img.shields.io/badge/DSH%20Web-Plugin-0f766e.svg)](https://github.com/MichengAI/dsh-archive-manager)
17
- [![Node.js 22 or later](https://img.shields.io/badge/Node.js-22%20or%20later-339933.svg?logo=node.js&logoColor=white)](https://nodejs.org/)
18
17
  </div>
19
18
 
20
19
  > DSH Archive Manager is a community-maintained DeepSeek Harness (DSH) plugin, not an official DeepSeek AI product.
21
20
 
22
- ## Features
21
+ ## What you can do
23
22
 
24
- Put inactive conversations away and find them again when needed. Search, restore, and clean up archives in one place while keeping everyday task lists tidy.
23
+ Put inactive conversations away and find them again when needed, keeping everyday task lists tidy.
25
24
 
26
- - **Put finished tasks away**: archive one conversation or all active chats in a workspace.
27
- - **Find past work**: search titles, filter by project, and sort by time or title in **Settings → Archived sessions**.
28
- - **Resume your work**: restore one conversation, a project group, or all archived chats.
29
- - **Clean up records**: permanently delete individual chats or batches after confirmation. **Permanent deletion cannot be undone.**
25
+ - **Archive conversations**: put away one chat or all unarchived chats in a workspace.
26
+ - **Find past work**: search titles, filter by project, and sort by time or title.
27
+ - **Restore tasks**: restore one chat, selected chats, a project group, or all archives.
28
+ - **Clean up records**: permanently delete unwanted archived conversations after confirmation.
30
29
 
31
30
  ## Screenshots
32
31
 
33
- Open the sidebar session menu and choose **Archive session**:
32
+ Archive a chat from the sidebar session menu:
34
33
 
35
34
  ![Archive a session from the session menu](assets/screenshots/archive-session-menu.png)
36
35
 
37
- Search, sort, filter by project, unarchive, or permanently delete chats in **Settings → Archived sessions**:
36
+ Find, restore, and clean up chats in **Settings → Archived sessions**:
38
37
 
39
38
  ![Archived sessions settings page](assets/screenshots/archived-sessions.png)
40
39
 
41
- ## DSH product ecosystem
42
-
43
- For a ready-to-use workbench, download [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases). If you already use [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), install any of these eight plugins individually. The desktop app includes all eight.
44
-
45
- | Plugin | What you can do |
46
- | --- | --- |
47
- | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | Organize projects and conversations, search tasks, and navigate chat turns |
48
- | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | Send tasks and receive replies through your usual messenger |
49
- | [Automation](https://github.com/MichengAI/dsh-automation) | Schedule tasks and review each run |
50
- | [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | Find, enable, create, and import local skills |
51
- | [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | Search, restore, or clean up archived conversations |
52
- | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | Choose and summon specialists for your task |
53
- | [BTW](https://github.com/MichengAI/dsh-btw) | Ask side questions without interrupting the main task |
54
- | [Simplify](https://github.com/MichengAI/dsh-simplify) | Use /simplify to improve code within your Git changes |
55
-
56
40
  ## Prerequisites
57
41
 
58
- - Plugin `0.1.38` supports DeepSeek Harness `0.1.5-rc.2` and retains the four previously supported hosts below. Later versions require separate validation.
59
-
60
- - A working DeepSeek Harness Web installation with `dsh` available in PowerShell.
61
- - Examples use the `web` profile; replace it with the target profile.
62
- - Source installation and development require Node.js 22+ and pnpm. npm installation does not require running `pnpm install` separately.
63
-
64
- DSH peer dependencies accept only `0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2`; other RCs and stable versions are not automatically accepted. Development dependencies are pinned to `0.1.5-rc.2`.
42
+ - A working [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web installation with `dsh` available in your terminal.
43
+ - Supported DSH versions: `0.1.0-rc.8`, `0.1.1-rc.2`, `0.1.2-rc.1`, `0.1.5-rc.1`, and `0.1.5-rc.2`. Other versions are not currently supported.
44
+ - Node.js matching `^22.19.0 || >=24.0.0`. Source installation also requires pnpm.
65
45
 
66
46
  ## Installation
67
47
 
68
- Run `pnpm test:compat` to test the same plugin artifacts with isolated host dependencies. The following combinations have passed; this does not claim coverage of every intermediate release:
69
-
70
- - `pnpm test`: build and run local tests, excluding `test/fixtures`; local host links may affect dependency resolution.
71
- - `pnpm test:matrix`: build, then install five isolated host versions and run the full matrix.
72
- - `pnpm test:latest`: build, then run the real storage regressions against the isolated latest baseline.
73
- - `pnpm test:compat`: validate legacy cache migration and run the full matrix.
74
-
75
- Do not run `test/fixtures/*.mjs` directly. Fixtures validate the isolated entry point, dependency versions, and resolved paths before loading the host. If local dependencies have drifted, run `pnpm install --frozen-lockfile` to restore declared development dependencies. This does not guarantee removal of undeclared packages or host links under `test/node_modules`; use the isolated commands for compatibility acceptance.
48
+ Examples use the `web` profile. Replace it with the profile you actually use.
76
49
 
77
- | DSH | Cordis | Automated regression |
78
- | --- | --- | --- |
79
- | `0.1.0-rc.8` | `4.0.1` | 153 passed |
80
- | `0.1.1-rc.2` | `4.0.1` | 153 passed, plus legacy cache migration validation |
81
- | `0.1.2-rc.1` | `4.0.2` | 153 passed |
82
- | `0.1.5-rc.1` | `4.0.2` | 162 passed |
83
- | `0.1.5-rc.2` | `4.0.2` | 162 passed |
50
+ ### Ask an agent to install it
84
51
 
85
- Coverage includes official workspace composition, sidebar shadowing and restoration on unload, directory-slot lifecycles, sidebar wiring, peer version acceptance, client Remote integration, archive/restore, real JSONL/Zstandard deletion and subagent cascades, and queries/reopened storage after deletion. Tested on Windows / Node.js 24. A real browser acceptance run also passed in an isolated DSH 0.1.5-rc.1 Web Profile: package installation, archive/restore, deletion cancellation and confirmation, subagent cascades, cross-filter batch deletion, workspace selection, returning from global panels, new sessions and forks, content search, and restart persistence. Content search requires an open host query database; it passed after changing the isolated Profile from `openAt: never` to `startup`. This release also passed an isolated rc.2 Web smoke run covering settings loading, seeded archive listing, restore, deletion cancellation and confirmation, and restart persistence with dedicated JSONL data. The full rc.1 browser flow was not repeated item by item. The other three versions have isolated automated coverage only; no external model calls were made. The latest storage fixture isolates only the upstream POSIX `fs-ext` import that cannot load on Windows; file operations and native Windows locking still use the official implementation.
86
-
87
- The installation commands below use the official npm registry.
88
-
89
- ### Ask an agent to install it (recommended)
90
-
91
- Send the prompt below to any agent that can run terminal commands on your computer. Replace `web` with your actual profile. Once installed, use the plugin in DSH.
52
+ Send this prompt to an agent that can run terminal commands on your computer:
92
53
 
93
54
  ```text
94
- Install the DSH plugin @michengai/dsh-archive-manager into my local web profile by running: dsh plugin --profile web add @michengai/dsh-archive-manager@latest --registry=https://registry.npmjs.org/. Then run dsh --profile web --dump-config, confirm the configuration includes workspace-archive-manager, ui-workspace-archive-manager, and explain how to reload DSH and start using the plugin.
55
+ Install the latest @michengai/dsh-archive-manager into my local DSH web profile using the official npm registry. Check the plugin configuration afterward, then explain how to reload DSH and open archived session management.
95
56
  ```
96
57
 
97
- ### Install the latest package from the official npm registry
58
+ ### Install manually
98
59
 
99
- Run this from any PowerShell directory:
60
+ Run in PowerShell:
100
61
 
101
62
  ```powershell
102
63
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
103
64
  $OutputEncoding = [System.Text.Encoding]::UTF8
65
+
104
66
  dsh plugin --profile web add @michengai/dsh-archive-manager@latest --registry=https://registry.npmjs.org/
105
- dsh --profile web --dump-config
106
67
  ```
107
68
 
108
- To pin a release, replace `@latest` with a specific version such as `@x.y.z`.
69
+ Restart DSH Web, then hard-refresh your browser with `Ctrl+Shift+R`. Open **Settings → Archived sessions** to get started.
109
70
 
110
- The configuration output should contain `workspace-archive-manager` and `ui-workspace-archive-manager`, with the official `ui-workspace` still enabled. Restart DSH Web and hard-refresh the browser. Do not copy client files manually: the Settings page and archive menu need the mounted plugin.
71
+ ## Usage
111
72
 
112
- ### Upgrade checks for existing installations
73
+ | Goal | Action |
74
+ | --- | --- |
75
+ | Archive one chat | Open its sidebar menu and choose **Archive session** |
76
+ | Archive a workspace | Open the workspace menu and choose the option to archive its chats |
77
+ | Find an archive | Open **Settings → Archived sessions**, then search titles or filter by project |
78
+ | Change the order | Sort by update time, creation time, or title |
79
+ | Restore one chat | Click **Unarchive** beside the session |
80
+ | Restore or delete in bulk | Select chats and use the bulk actions, or use the project menu or page-wide actions |
113
81
 
114
- Upgrades reload the patch bundled with the plugin; the old bundle needs no manual migration. However, the Profile's own `cordis.patch.yml` is applied after bundles. If it manually sets `ui-workspace` to `disabled: true`, remove that override or change it to `disabled: false`, preserving other configuration.
82
+ Selections persist when filters change. Check the hidden selection count before applying bulk actions, or clear your selection first.
115
83
 
116
- Run `dsh --profile web --dump-config`, confirm that the official `ui-workspace` is enabled, then restart DSH Web and hard-refresh the browser. On supported DSH versions from 0.1.2 onward, disabling that service leaves the archive client waiting for `uiWorkspace`; neither its sidebar nor its archive settings page mounts, which can look unresponsive.
84
+ ### View and continue archived conversations
117
85
 
118
- Sidebar directory creation depends on the official `sidebar.workspaces.directoryFlow` slot. If the source slot is absent or has no directory component, the entry is hidden without logging a synchronization error; use the official home entry instead. Directory extensions declaring additional child slots cannot be mirrored and produce a warning.
86
+ Available starting with `0.1.40`:
119
87
 
120
- A dynamic synchronization failure after mounting logs the source slot, target slot, and component origin when available, attempts to clear existing mirrors, and keeps listening to retry on a subsequent source-slot change. Removing a conflicting entry from the target slot alone does not trigger a retry. An initial synchronization failure instead unsubscribes, attempts cleanup, and rethrows to the mounting caller. After resolving the conflict, remount the archive client, for example by restarting DSH Web and hard-refreshing the browser. If releasing an entry fails during cleanup, that error is logged separately and cleanup continues for the remaining entries.
88
+ - **View conversation**: open the native DSH session to view messages, attachments, and tool details. Continue chatting while keeping the session archived.
89
+ - **Restore and open**: unarchive the session and open it to resume work.
121
90
 
122
91
  ## Updates
123
92
 
124
- The settings title shows the installed version and a **Check for updates** button. When a newer release is available, **Update automatically** runs only when the DSH CLI or Desktop update service is available; otherwise, the dialog provides a profile-specific manual command to copy and run.
93
+ Click **Check for updates** in the archive management page header. DSH CLI or Desktop environments with automatic update support can update directly; other environments provide a manual command for the current profile. You can also rerun the installation command above.
125
94
 
126
- ## Usage
95
+ ## FAQ
127
96
 
128
- 1. Open the sidebar session menu and choose **Archive session**.
129
- 2. Open **Settings → Archived sessions** to inspect sessions by workspace.
130
- 3. Search by title, sort by update time, creation time, or title, or filter the list by project.
131
- 4. Select **Unarchive** to restore one session, or select **Restore all** in the page header.
132
- 5. Open a project heading's action menu to restore or delete all of that project's archived chats.
133
- 6. Use the delete icon to remove one session permanently, then confirm the deletion. **It cannot be undone.**
97
+ ### Why is the entry missing after installation?
134
98
 
135
- If the entry is missing after installation or upgrade, restart DSH Web and hard-refresh the browser. It is located directly after **Connectors** in Settings.
136
-
137
- ## Data handling limits
138
-
139
- - Deletion always requires confirmation.
140
- - It removes workspace records, archive markers, and the projection cache. For the official JSONL backend, a validated layout also allows removal of the session-owned directory and its contents, including attachments. Other backends or unknown layouts only lose the located transcript artifact, never its parent directory.
141
- - Project containers and storage roots are retained. Deletion refuses symbolic links or Windows junctions at the official layout's project/session directory levels and keeps the operation retryable.
142
- - Layout validation uses the official backend's initialized absolute root, so a relative root remains stable across host working-directory changes. If that field is unavailable, only an absolute configured root is accepted. An unverified official JSONL layout emits a warning with the session ID and artifact path before falling back to artifact-only deletion.
143
- - Directory validation is not a cross-process filesystem lock: do not concurrently move or replace storage directories or change directory links during deletion. Storage paths writable by untrusted processes are not a security isolation boundary.
144
- - A live session finishes writing before cleanup to prevent data truncation.
145
- - The official `ui-workspace` remains enabled, so the home picker and navigation follow the host. The archive sidebar uses priority `-0.5`, ahead of the official default but behind Codex UI 1.1.2 at `-1`. Codex UI retains its list, styling, and interactions; archive management remains available in settings. Unloading Codex UI restores the archive sidebar; unloading the archive frontend restores the official sidebar. Backend workspace and projection services still use the archive implementations.
146
- - The sidebar directory entry reuses official leaf components through a separate child slot and follows their registration lifetimes. It does not redeclare official directory slots. Custom directory extensions with additional child slots should use the official home entry; this adapter does not transfer their child-slot permissions.
147
- - Install through the DSH profile instead of manually composing the patch.
148
-
149
- ## Secondary development
150
-
151
- ### Install from source
152
-
153
- Use this for debugging or unpublished changes. The cloned directory becomes the plugin source path:
99
+ Restart DSH Web and hard-refresh your browser. Make sure you installed into the profile you are using. If the entry is still missing, run:
154
100
 
155
101
  ```powershell
156
102
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
157
103
  $OutputEncoding = [System.Text.Encoding]::UTF8
158
- Set-Location D:\Repository\deepseek-harness-plugin
159
- git clone https://github.com/MichengAI/dsh-archive-manager.git
160
- Set-Location .\dsh-archive-manager
161
- pnpm install --frozen-lockfile
162
- pnpm build
163
- dsh plugin --profile web add .
104
+
164
105
  dsh --profile web --dump-config
165
106
  ```
166
107
 
167
- Restart DSH Web and hard-refresh the browser. `dsh plugin ... add .` reads the package metadata and `cordis.patch.yml`; do not install by copying `lib` directly.
108
+ The configuration should include `workspace-archive-manager` and `ui-workspace-archive-manager`. If you previously set the official `ui-workspace` to `disabled: true` in your profile's `cordis.patch.yml`, remove that disabling override and restart.
168
109
 
169
- `src` is the sole maintained source directory. `pnpm build` uses esbuild to compile it into publishable `lib` output. Do not edit `lib` directly because the next build overwrites it.
110
+ ### How is archiving different from deletion?
170
111
 
171
- - [src\index.js](src/index.js): host service entry point.
172
- - [src\workspace.js](src/workspace.js): archived-session and workspace service.
173
- - [src\projcache.js](src/projcache.js): session projection cache.
174
- - [src\client.js](src/client.js): Settings page and archive UI.
175
- - `test\*.test.mjs`: host, client, Remote, and styling coverage.
112
+ Archiving puts a conversation away so you can restore it later. **Permanent deletion cannot be undone** and may also remove that session's attachments. It does not delete your project working directory. Deletion requires confirmation.
176
113
 
177
- After changing `src`, run the tests, confirm that generated `lib` output is committed with the source, then install from the local directory:
114
+ ### Can I use it with Codex UI?
178
115
 
179
- ```powershell
180
- [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
181
- $OutputEncoding = [System.Text.Encoding]::UTF8
182
- pnpm test
183
- pnpm pack:check
184
- dsh plugin --profile web add .
185
- ```
116
+ Yes. [Codex UI](https://github.com/MichengAI/dsh-codex-ui) keeps its sidebar appearance and interactions. Archive management remains available in **Settings → Archived sessions**.
117
+
118
+ For other problems, open an [issue](https://github.com/MichengAI/dsh-archive-manager/issues) with your DSH and plugin versions, reproduction steps, and error details.
186
119
 
187
- `pnpm test` runs `pnpm build` first. The build generates all `lib` output from `src` in a temporary directory, then atomically replaces the previous output only after a successful build. A failed build preserves the existing `lib`.
120
+ ## Install from source
188
121
 
189
- ## Validation
122
+ <details>
123
+ <summary>Expand for development or testing unreleased changes</summary>
124
+
125
+ Run these commands in a directory of your choice. For local changes that have not been pushed, use the existing working copy.
190
126
 
191
127
  ```powershell
192
128
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
193
129
  $OutputEncoding = [System.Text.Encoding]::UTF8
194
- pnpm verify
130
+
131
+ git clone https://github.com/MichengAI/dsh-archive-manager.git
132
+ Set-Location .\dsh-archive-manager
133
+ pnpm install --frozen-lockfile
134
+ pnpm build
135
+ dsh plugin --profile web add .
195
136
  ```
196
137
 
197
- `prepublishOnly` runs the full verification suite before publishing and verifies that committed `lib` output matches the current `src` build.
138
+ Restart DSH Web and hard-refresh your browser afterward. Edit [src](src), not the generated `lib` directory. Run `pnpm test` to validate changes or `pnpm verify` for the full checks.
198
139
 
199
- ## License
140
+ </details>
200
141
 
201
- Licensed under [Apache License 2.0](LICENSE).
142
+ ## DSH product ecosystem
202
143
 
203
- Before creating or pushing a release tag, run `pnpm release:preflight` in PowerShell 7 (`pwsh`). It installs from the official npm registry with a frozen lockfile and the same 1440-minute release-age policy as CI, then runs the full `verify` suite. Recent dependencies require committed, exact-version exclusions; manual retries never add exclusions dynamically.
144
+ For a desktop workbench, download [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases). Existing [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) installations can add plugins as needed by following each project's README. Below are 11 first-party plugins; consult the corresponding desktop release notes and bundled catalog for what that version includes.
204
145
 
205
- Tag releases and manual retries verify the exact npm version and `gitHead` before creating or updating the bilingual GitHub Release. Network, authorization, or metadata errors stop the flow. Retrying an older tag does not take GitHub Latest away from the npm latest version. These release controls do not change the four supported host versions.
146
+ | Plugin | What you can do |
147
+ | --- | --- |
148
+ | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | Organize projects and conversations, search tasks, and navigate chat turns |
149
+ | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | Choose and summon specialists for your task |
150
+ | [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | Find, enable, create, and import local skills |
151
+ | [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | Search, restore, or clean up archived conversations |
152
+ | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | Send tasks and receive replies through messaging platforms |
153
+ | [Automation](https://github.com/MichengAI/dsh-automation) | Schedule tasks and review each run |
154
+ | [BTW](https://github.com/MichengAI/dsh-btw) | Ask side questions without interrupting the main task |
155
+ | [Simplify](https://github.com/MichengAI/dsh-simplify) | Use `/simplify` to improve code within your Git changes |
156
+ | [PUA](https://github.com/MichengAI/dsh-pua) | Guide the Agent to try new approaches after failures, investigate causes, and verify results before completion |
157
+ | [Code Review](https://github.com/MichengAI/dsh-code-review) | Use `/review` to request an independent Agent code review and receive the report in the current conversation |
158
+ | [Codex Pet](https://github.com/MichengAI/dsh-codex-pet) | View conversation notifications and respond to tool approvals and questions through a desktop pet |
206
159
 
207
- Recovery checks the npm package name, version, and tag commit first. An exact match skips dependency installation, full builds, and republishing, and only synchronizes the Release. Unpublished old tags must still satisfy their original cooling policy. Exact-version and latest propagation each have a two-minute deadline; a lagging latest tag does not create a non-Latest Release prematurely.
208
160
 
209
- Updating main does not retrigger an existing tag. After pushing workflow changes, manually run `publish.yml` on main with the original tag (for example, `v0.1.35`); do not move or recreate the tag.
161
+ ## License
162
+
163
+ Licensed under [Apache License 2.0](LICENSE).
package/README.zh-CN.md CHANGED
@@ -14,196 +14,150 @@
14
14
  [![npm package](https://img.shields.io/npm/v/%40michengai%2Fdsh-archive-manager.svg?label=npm%20package)](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
15
15
  [![npm 下载量](https://img.shields.io/npm/dt/%40michengai%2Fdsh-archive-manager.svg?label=npm%20%E4%B8%8B%E8%BD%BD%E9%87%8F)](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
16
16
  [![DSH Web Plugin](https://img.shields.io/badge/DSH%20Web-Plugin-0f766e.svg)](https://github.com/MichengAI/dsh-archive-manager)
17
- [![Node.js 22 or later](https://img.shields.io/badge/Node.js-22%20or%20later-339933.svg?logo=node.js&logoColor=white)](https://nodejs.org/)
18
17
  </div>
19
18
 
20
19
  > DSH Archive Manager 是社区维护的 DeepSeek Harness(DSH)插件,并非 DeepSeek AI 官方产品。
21
20
 
22
- ## 功能概览
21
+ ## 你可以用它做什么
23
22
 
24
- 把暂时不用的会话收起来,需要时再找回。集中搜索、恢复和清理归档记录,让日常任务列表更清爽。
23
+ 把暂时不用的会话收起来,需要时再找回,让日常任务列表更清爽。
25
24
 
26
- - **收起已完成的任务**:归档单条聊天,也可归档整个工作区的未归档聊天。
27
- - **快速找回历史**:在「设置 → 归档会话」搜索标题、按项目筛选,或按时间和标题排序。
28
- - **恢复继续工作**:恢复单条会话、整个项目或全部归档会话。
29
- - **按需清理记录**:支持单条或批量永久删除,执行前会要求确认。**永久删除无法撤销。**
25
+ - **归档会话**:收起单条聊天,或整个工作区的未归档聊天。
26
+ - **找回历史**:搜索会话标题,按项目筛选,按时间或标题排序。
27
+ - **恢复任务**:恢复单条、选中的会话、整个项目或全部归档会话。
28
+ - **清理记录**:确认后永久删除不再需要的归档会话。
30
29
 
31
30
  ## 界面预览
32
31
 
33
- 在侧栏会话菜单中选择「归档会话」:
32
+ 从侧栏会话菜单归档聊天:
34
33
 
35
34
  ![从会话菜单归档会话](assets/screenshots/archive-session-menu.png)
36
35
 
37
- 在「设置 → 归档会话」中搜索、排序、按项目筛选、取消归档或永久删除:
36
+ 在「设置 → 归档会话」集中查找、恢复和清理:
38
37
 
39
- ![已归档聊天设置页面](assets/screenshots/archived-sessions.png)
40
-
41
- ## DSH 产品生态
42
-
43
- 想直接使用完整工作台,可下载 [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases);已有 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 环境,可按需独立安装以下 8 个自研插件。桌面端已随附这些插件。
44
-
45
- | 插件 | 你可以用它做什么 |
46
- | --- | --- |
47
- | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | 整理项目与会话、搜索任务、跳转对话轮次 |
48
- | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | 从微信、飞书、钉钉等消息平台下任务、收回复 |
49
- | [Automation](https://github.com/MichengAI/dsh-automation) | 按计划执行任务,查看每次运行的结果 |
50
- | [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | 统一查找、启停、创建和导入本机技能 |
51
- | [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | 搜索、恢复或清理已归档会话 |
52
- | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | 按任务选择并召唤专业角色 |
53
- | [BTW](https://github.com/MichengAI/dsh-btw) | 在当前上下文中临时旁问,不打断主任务 |
54
- | [Simplify](https://github.com/MichengAI/dsh-simplify) | 用 /simplify 整理 Git 改动范围内的代码 |
38
+ ![归档会话管理页](assets/screenshots/archived-sessions.png)
55
39
 
56
40
  ## 前置条件
57
41
 
58
- - 插件 `0.1.38` 支持 DeepSeek Harness `0.1.5-rc.2`,并保留下表中此前四个宿主版本的兼容路径。后续版本需另行验证。
59
-
60
- - 已可正常运行 DeepSeek Harness Web,且可在 PowerShell 中使用 `dsh`。
61
- - 以下示例使用 `web` profile;请替换为实际目标 profile。
62
- - 从源码安装或二次开发需要 Node.js 22+ 与 pnpm;仅从 npm 安装无需另外执行 `pnpm install`。
63
-
64
- DSH peer 依赖仅接受 `0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2`;不自动接纳其他 RC 或正式版。开发依赖固定为 `0.1.5-rc.2`。
42
+ - 已能正常使用 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web,并可在终端运行 `dsh`。
43
+ - 当前支持 DSH `0.1.0-rc.8`、`0.1.1-rc.2`、`0.1.2-rc.1`、`0.1.5-rc.1`、`0.1.5-rc.2`;其他版本暂未纳入支持范围。
44
+ - Node.js 版本需满足 `^22.19.0 || >=24.0.0`;从源码安装还需要 pnpm。
65
45
 
66
46
  ## 安装
67
47
 
68
- 源码回归使用 `pnpm test:compat`,在隔离依赖环境中运行同一份插件产物,已验证以下组合(不代表覆盖所有中间版本):
69
-
70
- - `pnpm test`:构建并执行本地常规测试,不包含 `test/fixtures`;实际依赖可能受本机宿主链接影响。
71
- - `pnpm test:matrix`:先构建,再隔离安装五个宿主版本,执行完整矩阵。
72
- - `pnpm test:latest`:先构建,再隔离运行最新版的 真实存储回归。
73
- - `pnpm test:compat`:旧缓存迁移验证加完整矩阵。
74
-
75
- 不要直接执行 `test/fixtures/*.mjs`;夹具会在加载宿主前检查隔离入口、依赖版本及实际路径。若本机依赖脱节,可先执行 `pnpm install --frozen-lockfile` 恢复声明的开发依赖;它不保证清理未声明包或 `test/node_modules` 的宿主链接,兼容性验收以隔离命令为准。
48
+ 以下示例使用 `web` profile,请替换为你实际使用的 profile。
76
49
 
77
- | DSH | Cordis | 自动回归 |
78
- | --- | --- | --- |
79
- | `0.1.0-rc.8` | `4.0.1` | 153 项通过 |
80
- | `0.1.1-rc.2` | `4.0.1` | 153 项通过,另含旧缓存迁移验证 |
81
- | `0.1.2-rc.1` | `4.0.2` | 153 项通过 |
82
- | `0.1.5-rc.1` | `4.0.2` | 162 项通过 |
83
- | `0.1.5-rc.2` | `4.0.2` | 162 项通过 |
50
+ ### 让 Agent 帮你安装
84
51
 
85
- 覆盖官方工作区插件组合、侧栏覆盖及卸载恢复、目录插槽生命周期、侧栏接线、peer 版本接纳、客户端 Remote、归档/恢复、真实 JSONL/Zstandard 删除与子会话级联、删除后重新查询及重新打开存储。验证环境为 Windows / Node.js 24。另已在隔离 DSH 0.1.5-rc.1 Web Profile 中完成真实浏览器验收:包安装、归档恢复、取消与确认删除、子会话级联、跨筛选批量删除、工作区选择、全局面板返回会话、新建与分叉、正文搜索和重启持久性均通过。正文搜索需开启宿主查询数据库;本次将隔离 Profile 的 `openAt: never` 改为 `startup` 后验证通过。本轮另在隔离 rc.2 Web Profile 完成设置页加载、预置归档记录展示、恢复、取消与确认删除及重启持久性冒烟验收;使用独立 JSONL 测试数据,未逐项重跑 rc.1 的完整浏览器流程。其余三个版本仅完成隔离自动测试;未调用外部模型。新版存储夹具仅隔离上游无法在 Windows 加载的 POSIX `fs-ext` 导入,实际文件操作与 Windows 原生锁仍使用官方实现。
86
-
87
- 以下安装命令使用官方 npm 源。
88
-
89
- ### 让 Agent 帮你安装(推荐)
90
-
91
- 把下面这段话发给任意能够执行本机终端命令的 Agent。将 `web` 替换为实际使用的 profile;安装完成后,在 DSH 中使用本插件。
52
+ 把下面这段话发给能执行本机终端命令的 Agent:
92
53
 
93
54
  ```text
94
- 请将 DSH 插件 @michengai/dsh-archive-manager 安装到本机 web profile,执行:dsh plugin --profile web add @michengai/dsh-archive-manager@latest --registry=https://registry.npmjs.org/。安装后执行 dsh --profile web --dump-config,确认配置包含 workspace-archive-manager, ui-workspace-archive-manager,并告诉我如何重新加载 DSH 和开始使用。
55
+ 请将 @michengai/dsh-archive-manager 最新版安装到本机 DSH 的 web profile,使用官方 npm 源。安装后检查插件配置,并告诉我如何重新加载 DSH、进入归档会话管理页。
95
56
  ```
96
57
 
97
- ### 从官方 npm 安装最新版
58
+ ### 手动安装
98
59
 
99
- 在任意 PowerShell 目录执行:
60
+ 在 PowerShell 中执行:
100
61
 
101
62
  ```powershell
102
63
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
103
64
  $OutputEncoding = [System.Text.Encoding]::UTF8
65
+
104
66
  dsh plugin --profile web add @michengai/dsh-archive-manager@latest --registry=https://registry.npmjs.org/
105
- dsh --profile web --dump-config
106
67
  ```
107
68
 
108
- 需要钉死某一版时,把 `@latest` 换成具体版本,例如 `@x.y.z`。
109
-
110
- 配置输出中应包含 `workspace-archive-manager` 与 `ui-workspace-archive-manager`,同时官方 `ui-workspace` 保持启用。安装后重启 DSH Web 并在浏览器硬刷新;请勿手工复制客户端文件,否则设置页和归档菜单不会被挂载。
111
-
112
- ### 已安装环境升级检查
113
-
114
- 升级会重新读取插件包内的补丁,无需手工迁移旧 bundle。但 Profile 自己的 `cordis.patch.yml` 在 bundle 之后应用:若曾手工将 `ui-workspace` 设为 `disabled: true`,请移除该覆盖或改为 `disabled: false`,保留其余配置。
69
+ 安装后重启 DSH Web,并按 `Ctrl+Shift+R` 硬刷新浏览器。打开「设置 → 归档会话」即可使用。
115
70
 
116
- 运行 `dsh --profile web --dump-config`,确认官方 `ui-workspace` 已启用,再重启 DSH Web 并硬刷新浏览器。在 DSH 0.1.2 及以上受支持版本中,禁用官方服务会让归档客户端等待 `uiWorkspace`,侧栏与归档设置页都不会挂载,可能表现为没有响应。
117
-
118
- 侧栏添加目录依赖官方 `sidebar.workspaces.directoryFlow` 插槽。源槽不存在或没有目录组件时,该入口会隐藏,不会因此记录同步错误;可使用官方首页入口。带额外子插槽的目录扩展暂不支持镜像,会输出警告。
71
+ ## 使用
119
72
 
120
- 挂载后的动态同步异常会记录源槽、目标槽及可用的组件来源,并尝试清理已有镜像,保留监听以在后续源槽变化时重试。仅移除目标槽的冲突条目不会触发重试。首次挂载时同步失败则会取消监听、尝试清理并向挂载调用方抛出错误;排除冲突后需重新挂载归档客户端,例如重启 DSH Web 并硬刷新浏览器。清理过程中若有条目释放失败,会另行记录错误并继续清理其余条目。
73
+ | 你想做什么 | 操作 |
74
+ | --- | --- |
75
+ | 归档一条会话 | 打开侧栏会话菜单,选择「归档会话」 |
76
+ | 归档整个工作区 | 打开工作区菜单,选择归档该工作区的会话 |
77
+ | 查找归档 | 打开「设置 → 归档会话」,搜索标题或按项目筛选 |
78
+ | 调整排列顺序 | 按更新时间、创建时间或标题排序 |
79
+ | 恢复一条会话 | 点击会话右侧的「取消归档」 |
80
+ | 批量恢复或删除 | 勾选会话后使用批量操作;也可使用项目菜单或页面顶部的全部操作 |
121
81
 
122
- ## 在线更新
82
+ 切换筛选条件会保留已选会话。批量操作前留意隐藏的已选数量,或先清空选择。
123
83
 
124
- 设置标题会显示当前版本和“检查更新”按钮。发现新版后,只有检测到 DSH CLI 或 Desktop 更新服务时才可使用“自动更新”;其他环境会在弹窗中提供可复制、与当前 Profile 对应的手工更新命令。
84
+ ### 查看并继续归档对话
125
85
 
126
- ## 使用
86
+ 从 `0.1.40` 起支持以下操作:
127
87
 
128
- 1. 在侧栏右键或打开会话菜单,选择「归档会话」。
129
- 2. 打开「设置 → 归档会话」,按工作区查看归档会话。
130
- 3. 按标题搜索,按更新时间、创建时间或标题排序,或按项目筛选列表。
131
- 4. 点击「取消归档」恢复单个会话,或在顶部点击「全部恢复」。
132
- 5. 打开项目标题右侧菜单,可恢复或删除该项目的全部已归档聊天。
133
- 6. 点击删除图标永久移除单个会话;删除前确认提示。**删除无法撤销。**
88
+ - **查看对话**:打开 DSH 原生会话页,查看消息、附件和工具详情;可直接继续聊天,保持归档状态。
89
+ - **恢复并打开**:取消归档后进入原会话,继续工作。
134
90
 
135
- 安装或升级后找不到入口时,重启 DSH Web 并硬刷新浏览器;入口位于「设置」中,连接器之后。
91
+ ## 更新
136
92
 
137
- ## 数据处理边界
93
+ 在归档管理页标题处点击「检查更新」。支持自动更新的 DSH CLI 或 Desktop 环境可直接更新;其他环境会提供适用于当前 profile 的手动命令。也可重新执行上面的安装命令。
138
94
 
139
- - 删除操作始终需要确认。
140
- - 删除会移除工作区记录、归档标记和投影缓存。官方 JSONL 后端在目录布局校验通过后,会一并删除会话专属目录及其中的附件等内容;其他后端或未知布局仅删除定位到的转录工件,不删除其父目录。
141
- - 不清理项目分组目录或存储根目录。官方布局的项目/会话目录若为符号链接或 Windows junction,会拒绝删除并保留可重试状态。
142
- - 布局校验优先使用官方后端初始化时确定的绝对根路径,相对路径配置不再受宿主工作目录变化影响;若无法取得该字段,则只接受绝对路径配置。官方 JSONL 布局校验失败时,会记录包含会话 ID 和工件路径的警告,再降级为仅删除工件。
143
- - 目录校验不是跨进程文件锁:删除期间不要由其他进程迁移、替换存储目录或改写目录链接。插件不将可被不可信进程改写的存储路径视为安全隔离边界。
144
- - 正在写入的会话会在完成写入后清理,避免截断数据。
145
- - 本插件保留官方 `ui-workspace`,首页工作区选择器与导航服务跟随宿主;以优先级 `-0.5` 覆盖官方默认侧栏;与 Codex UI 1.1.2 共存时保留其优先级 `-1` 的列表、样式与交互,归档管理仍在设置页提供。卸载 Codex UI 后恢复归档侧栏;卸载归档前端后恢复官方侧栏。后端工作区和会话投影服务仍由归档实现提供。
146
- - 侧栏目录入口复用官方目录叶组件,使用独立子插槽,随官方条目加载、卸载同步;不重复声明官方目录插槽。自定义目录扩展若声明额外子插槽,应使用官方首页入口;当前适配不转移其子插槽权限。
147
- - 请仅通过 DSH profile 安装,避免手工拼接补丁配置。
95
+ ## 常见问题
148
96
 
149
- ## 二次开发
97
+ ### 安装后找不到入口?
150
98
 
151
- ### 从源码安装
152
-
153
- 适用于调试或使用未发布改动。克隆后的目录会直接作为插件安装路径:
99
+ 先重启 DSH Web 并硬刷新浏览器,确认安装到了当前使用的 profile。仍未显示时,执行:
154
100
 
155
101
  ```powershell
156
102
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
157
103
  $OutputEncoding = [System.Text.Encoding]::UTF8
158
- Set-Location D:\Repository\deepseek-harness-plugin
159
- git clone https://github.com/MichengAI/dsh-archive-manager.git
160
- Set-Location .\dsh-archive-manager
161
- pnpm install --frozen-lockfile
162
- pnpm build
163
- dsh plugin --profile web add .
104
+
164
105
  dsh --profile web --dump-config
165
106
  ```
166
107
 
167
- 完成后重启 DSH Web 并硬刷新浏览器。`dsh plugin ... add .` 会读取当前目录的包信息和 `cordis.patch.yml`;不要改为直接复制 `lib` 目录。
108
+ 配置中应包含 `workspace-archive-manager` 和 `ui-workspace-archive-manager`。若曾在 profile 的 `cordis.patch.yml` 中手动将官方 `ui-workspace` 设为 `disabled: true`,请移除该禁用覆盖,再重启。
168
109
 
169
- `src` 是唯一可维护源码目录,`pnpm build` 使用 esbuild 将其编译为可发布的 `lib`。请勿直接修改 `lib`,否则下次构建会覆盖改动:
110
+ ### 归档和删除有什么区别?
170
111
 
171
- - [src\index.js](src/index.js):客户端插件 Host 服务入口。
172
- - [src\workspace.js](src/workspace.js):归档会话和工作区服务实现。
173
- - [src\projcache.js](src/projcache.js):会话投影缓存实现。
174
- - [src\client.js](src/client.js):设置页和归档会话界面。
175
- - `test\*.test.mjs`:Host、客户端、Remote 和样式边界测试。
112
+ 归档只是收起会话,可以恢复。**永久删除无法撤销**,并可能一并清理该会话的附件;不会删除你的项目工作目录。删除前会要求确认。
176
113
 
177
- 修改 `src` 后,执行测试并确认生成的 `lib` 与 `src` 一同提交,再用本地目录重新安装:
114
+ ### 可以和 Codex UI 一起使用吗?
178
115
 
179
- ```powershell
180
- [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
181
- $OutputEncoding = [System.Text.Encoding]::UTF8
182
- pnpm test
183
- pnpm pack:check
184
- dsh plugin --profile web add .
185
- ```
116
+ 可以。保留 [Codex UI](https://github.com/MichengAI/dsh-codex-ui) 的侧栏样式和交互,归档管理仍在「设置 → 归档会话」中。
117
+
118
+ 遇到其他问题,请提交 [Issue](https://github.com/MichengAI/dsh-archive-manager/issues),附上 DSH 与插件版本、复现步骤和错误信息。
186
119
 
187
- `pnpm test` 会先执行 `pnpm build`。构建在临时目录中从 `src` 生成全部 `lib` 产物;仅在生成成功后才原子替换旧产物,构建失败时会保留旧的 `lib`。
120
+ ## 从源码安装
188
121
 
189
- ## 验证
122
+ <details>
123
+ <summary>开发或测试未发布改动时展开</summary>
124
+
125
+ 在你选择的源码目录中执行以下命令。未推送的本地改动需使用已有工作副本。
190
126
 
191
127
  ```powershell
192
128
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
193
129
  $OutputEncoding = [System.Text.Encoding]::UTF8
194
- pnpm verify
130
+
131
+ git clone https://github.com/MichengAI/dsh-archive-manager.git
132
+ Set-Location .\dsh-archive-manager
133
+ pnpm install --frozen-lockfile
134
+ pnpm build
135
+ dsh plugin --profile web add .
195
136
  ```
196
137
 
197
- `prepublishOnly` 会在发布前执行完整验证,并确认提交的 `lib` 与当前 `src` 构建结果一致。
138
+ 完成后重启 DSH Web 并硬刷新浏览器。修改 [src](src) 中的源码,不直接编辑生成目录 `lib`;使用 `pnpm test` 验证修改,使用 `pnpm verify` 执行完整检查。
198
139
 
199
- ## 许可证
140
+ </details>
200
141
 
201
- 本项目采用 [Apache License 2.0](LICENSE)。
142
+ ## DSH 产品生态
202
143
 
203
- 发布前在 PowerShell 7 (`pwsh`) 执行 `pnpm release:preflight`,通过后再创建、推送版本标签。该入口使用官方 npm registry、冻结锁文件及与 CI 一致的 1440 分钟依赖冷却策略,然后运行完整 `verify`。刚发布的依赖仅通过已提交的精确版本豁免接纳;手动重试不会动态修改豁免配置。
144
+ 想使用桌面工作台,可下载 [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases);已有 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 环境,可按各项目 README 按需安装。以下列出 11 个自研插件;桌面端实际随附范围以对应版本的发行说明和内置清单为准。
204
145
 
205
- 标签发布与手动重试统一经过 npm 精确版本和 `gitHead` 校验,确认 npm 可查询后才创建或更新双语 GitHub Release。网络、权限或元数据异常会停止流程;旧标签重试不会抢占 npm 最新版本对应的 GitHub Latest。仅新增了发布控制,不改变四个宿主版本的兼容范围。
146
+ | 插件 | 你可以用它做什么 |
147
+ | --- | --- |
148
+ | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | 整理项目与会话、搜索任务、跳转对话轮次 |
149
+ | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | 按任务选择并召唤专业角色 |
150
+ | [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | 统一查找、启停、创建和导入本机技能 |
151
+ | [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | 搜索、恢复或清理已归档会话 |
152
+ | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | 从消息平台下任务、收回复 |
153
+ | [Automation](https://github.com/MichengAI/dsh-automation) | 按计划执行任务,查看每次运行的结果 |
154
+ | [BTW](https://github.com/MichengAI/dsh-btw) | 在当前上下文中临时旁问,不打断主任务 |
155
+ | [Simplify](https://github.com/MichengAI/dsh-simplify) | 用 `/simplify` 整理 Git 改动范围内的代码 |
156
+ | [PUA](https://github.com/MichengAI/dsh-pua) | 引导 Agent 在失败时换方法、查原因,并在完成前验证结果 |
157
+ | [Code Review](https://github.com/MichengAI/dsh-code-review) | 用 `/review` 发起独立 Agent 代码审查,在当前会话接收报告 |
158
+ | [Codex Pet](https://github.com/MichengAI/dsh-codex-pet) | 通过桌面宠物查看会话提醒、处理工具审批和问题回答 |
206
159
 
207
- 恢复已发布版本时,流程先校验 npm 包名、版本和标签提交:一致则跳过依赖安装、完整构建和再次发布,仅补同步 Release;未发布的旧标签仍需满足原标签的冷却策略,不能通过重试绕过。精确版本及 latest 传播分别最多等待两分钟,latest 落后时不提前创建非 Latest Release。
208
160
 
209
- 已推送的旧标签不会因 main 更新而重新触发。推送工作流更新后,可手动运行 `publish.yml`,选择 main 并填写原标签(例如 `v0.1.35`);无需移动或重新创建标签。
161
+ ## 许可证
162
+
163
+ 本项目采用 [Apache License 2.0](LICENSE)。
@@ -0,0 +1,68 @@
1
+ class ArchiveNavigationError extends Error {
2
+ constructor(code) {
3
+ super(code);
4
+ this.name = "ArchiveNavigationError";
5
+ this.code = code;
6
+ }
7
+ }
8
+ function formatArchiveNavigationError(error, t) {
9
+ return error instanceof ArchiveNavigationError ? t(`archives.${error.code}`) : String(error?.message ?? error);
10
+ }
11
+ async function openArchivedConversation(actions, sessionId, restore, isActive = () => true) {
12
+ if (restore) await actions.restore(sessionId);
13
+ if (isActive()) await actions.open(sessionId);
14
+ }
15
+ function allowArchivedNavigation(navigation, sessions, workspaces, { onOpened = () => {
16
+ }, warn = (...args) => console.warn(...args) } = {}) {
17
+ let allowed;
18
+ const original = navigation?.clearArchivedCurrent;
19
+ const descriptor = navigation && Object.getOwnPropertyDescriptor(navigation, "clearArchivedCurrent");
20
+ const wrapped = function(...args) {
21
+ const current = sessions.list.getSnapshot().current;
22
+ if (current === allowed && current !== void 0 && workspaces.list.getSnapshot().archivedSessionIds.includes(current)) return false;
23
+ allowed = void 0;
24
+ return original.apply(this, args);
25
+ };
26
+ const ownsWrapper = () => navigation && Object.getOwnPropertyDescriptor(navigation, "clearArchivedCurrent")?.value === wrapped;
27
+ if (typeof original === "function") {
28
+ try {
29
+ navigation.clearArchivedCurrent = wrapped;
30
+ if (!ownsWrapper()) throw new Error("\u5BFC\u822A\u65B9\u6CD5\u672A\u63A5\u7EB3\u5F52\u6863\u9002\u914D");
31
+ } catch (error) {
32
+ warn("archive-manager: \u65E0\u6CD5\u9002\u914D\u5F52\u6863\u5BFC\u822A\uFF0C\u8BF7\u6062\u590D\u4F1A\u8BDD\u540E\u6253\u5F00\u3002", error);
33
+ }
34
+ }
35
+ return {
36
+ open(id) {
37
+ allowed = id;
38
+ try {
39
+ if (typeof original === "function" && !ownsWrapper() && workspaces.list.getSnapshot().archivedSessionIds.includes(id)) {
40
+ warn("archive-manager: \u5F52\u6863\u5BFC\u822A\u9002\u914D\u4E0D\u53EF\u7528\u6216\u5DF2\u88AB\u66FF\u6362\u3002");
41
+ throw new ArchiveNavigationError("navigationUnavailable");
42
+ }
43
+ sessions.open(id);
44
+ if (sessions.list.getSnapshot().current !== id) {
45
+ warn("archive-manager: \u5BBF\u4E3B\u672A\u4FDD\u7559\u76EE\u6807\u4F1A\u8BDD\u3002", { cleanupAvailable: typeof original === "function" });
46
+ throw new ArchiveNavigationError("sessionNotRetained");
47
+ }
48
+ onOpened();
49
+ } catch (error) {
50
+ allowed = void 0;
51
+ throw error;
52
+ }
53
+ },
54
+ dispose() {
55
+ allowed = void 0;
56
+ if (!navigation || Object.getOwnPropertyDescriptor(navigation, "clearArchivedCurrent")?.value !== wrapped) return;
57
+ if (descriptor) Object.defineProperty(navigation, "clearArchivedCurrent", descriptor);
58
+ else delete navigation.clearArchivedCurrent;
59
+ original.call(navigation);
60
+ }
61
+ };
62
+ }
63
+ export {
64
+ ArchiveNavigationError,
65
+ allowArchivedNavigation,
66
+ formatArchiveNavigationError,
67
+ openArchivedConversation
68
+ };
package/lib/client.js CHANGED
@@ -1,4 +1,68 @@
1
1
  (() => {
2
+ // src/archive-experience.js
3
+ var ArchiveNavigationError = class extends Error {
4
+ constructor(code) {
5
+ super(code);
6
+ this.name = "ArchiveNavigationError";
7
+ this.code = code;
8
+ }
9
+ };
10
+ function formatArchiveNavigationError(error, t) {
11
+ return error instanceof ArchiveNavigationError ? t(`archives.${error.code}`) : String(error?.message ?? error);
12
+ }
13
+ async function openArchivedConversation(actions, sessionId, restore, isActive = () => true) {
14
+ if (restore) await actions.restore(sessionId);
15
+ if (isActive()) await actions.open(sessionId);
16
+ }
17
+ function allowArchivedNavigation(navigation, sessions, workspaces, { onOpened = () => {
18
+ }, warn = (...args) => console.warn(...args) } = {}) {
19
+ let allowed;
20
+ const original = navigation?.clearArchivedCurrent;
21
+ const descriptor = navigation && Object.getOwnPropertyDescriptor(navigation, "clearArchivedCurrent");
22
+ const wrapped = function(...args) {
23
+ const current = sessions.list.getSnapshot().current;
24
+ if (current === allowed && current !== void 0 && workspaces.list.getSnapshot().archivedSessionIds.includes(current)) return false;
25
+ allowed = void 0;
26
+ return original.apply(this, args);
27
+ };
28
+ const ownsWrapper = () => navigation && Object.getOwnPropertyDescriptor(navigation, "clearArchivedCurrent")?.value === wrapped;
29
+ if (typeof original === "function") {
30
+ try {
31
+ navigation.clearArchivedCurrent = wrapped;
32
+ if (!ownsWrapper()) throw new Error("\u5BFC\u822A\u65B9\u6CD5\u672A\u63A5\u7EB3\u5F52\u6863\u9002\u914D");
33
+ } catch (error) {
34
+ warn("archive-manager: \u65E0\u6CD5\u9002\u914D\u5F52\u6863\u5BFC\u822A\uFF0C\u8BF7\u6062\u590D\u4F1A\u8BDD\u540E\u6253\u5F00\u3002", error);
35
+ }
36
+ }
37
+ return {
38
+ open(id) {
39
+ allowed = id;
40
+ try {
41
+ if (typeof original === "function" && !ownsWrapper() && workspaces.list.getSnapshot().archivedSessionIds.includes(id)) {
42
+ warn("archive-manager: \u5F52\u6863\u5BFC\u822A\u9002\u914D\u4E0D\u53EF\u7528\u6216\u5DF2\u88AB\u66FF\u6362\u3002");
43
+ throw new ArchiveNavigationError("navigationUnavailable");
44
+ }
45
+ sessions.open(id);
46
+ if (sessions.list.getSnapshot().current !== id) {
47
+ warn("archive-manager: \u5BBF\u4E3B\u672A\u4FDD\u7559\u76EE\u6807\u4F1A\u8BDD\u3002", { cleanupAvailable: typeof original === "function" });
48
+ throw new ArchiveNavigationError("sessionNotRetained");
49
+ }
50
+ onOpened();
51
+ } catch (error) {
52
+ allowed = void 0;
53
+ throw error;
54
+ }
55
+ },
56
+ dispose() {
57
+ allowed = void 0;
58
+ if (!navigation || Object.getOwnPropertyDescriptor(navigation, "clearArchivedCurrent")?.value !== wrapped) return;
59
+ if (descriptor) Object.defineProperty(navigation, "clearArchivedCurrent", descriptor);
60
+ else delete navigation.clearArchivedCurrent;
61
+ original.call(navigation);
62
+ }
63
+ };
64
+ }
65
+
2
66
  // src/directory-flow-slot.js
3
67
  function mirrorDirectoryFlow(ctx, target) {
4
68
  const source = "sidebar.workspaces.directoryFlow";
@@ -665,20 +729,48 @@
665
729
  function GithubMark16() {
666
730
  return (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": true, focusable: "false", children: (0, react_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M8 0a8 8 0 0 0-2.53 15.59c.4.074.547-.173.547-.385 0-.19-.007-.693-.01-1.36-2.226.484-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.872.87 2.328.665.072-.517.28-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.956 0-.874.31-1.588.823-2.148-.083-.202-.357-1.017.078-2.12 0 0 .672-.215 2.2.82A7.65 7.65 0 0 1 8 4.8c.68.003 1.365.092 2.004.27 1.527-1.035 2.197-.82 2.197-.82.437 1.103.162 1.918.08 2.12.513.56.822 1.274.822 2.148 0 3.076-1.872 3.752-3.654 3.95.288.248.544.735.544 1.482 0 1.07-.01 1.932-.01 2.195 0 .214.144.463.55.384A8.001 8.001 0 0 0 8 0Z" }) });
667
731
  }
668
- function ArchivedSessionsSectionPrototype({ sessionStore, workspaceStore, unarchiveSession, deleteSession, unarchiveSessions, deleteArchivedSessions, archivedSessionMetadata, t }) {
732
+ function ArchivedSessionsSection({ sessionStore, workspaceStore, unarchiveSession, deleteSession, unarchiveSessions, deleteArchivedSessions, archivedSessionMetadata, openConversation, viewState, close, t }) {
669
733
  const sessions = (0, react.useSyncExternalStore)(sessionStore.subscribe, sessionStore.getSnapshot);
670
734
  const workspaceState = (0, react.useSyncExternalStore)(workspaceStore.subscribe, workspaceStore.getSnapshot);
671
735
  const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
672
736
  const [busy, setBusy] = (0, react.useState)(false);
673
737
  const [error, setError] = (0, react.useState)(null);
674
738
  const [notice, setNotice] = (0, react.useState)(null);
675
- const [query, setQuery] = (0, react.useState)("");
676
- const [project, setProject] = (0, react.useState)("all");
677
- const [sortBy, setSortBy] = (0, react.useState)("updated");
739
+ const [query, setQuery] = (0, react.useState)(viewState?.query ?? "");
740
+ const [project, setProject] = (0, react.useState)(viewState?.project ?? "all");
741
+ const [sortBy, setSortBy] = (0, react.useState)(viewState?.sortBy ?? "updated");
678
742
  const [createdAtById, setCreatedAtById] = (0, react.useState)({});
679
743
  const [unarchivingSessionIds, setUnarchivingSessionIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
680
744
  const unarchivingSessionIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
681
745
  const [selectedSessionIds, setSelectedSessionIds] = (0, react.useState)([]);
746
+ const navigationActive = (0, react.useRef)(true);
747
+ const navigationPending = (0, react.useRef)(false);
748
+ (0, react.useEffect)(() => {
749
+ navigationActive.current = true;
750
+ return () => {
751
+ navigationActive.current = false;
752
+ };
753
+ }, []);
754
+ const viewConversation = async (session, restore = false) => {
755
+ if (busy || navigationPending.current) return;
756
+ navigationPending.current = true;
757
+ setBusy(true);
758
+ setError(null);
759
+ try {
760
+ await openArchivedConversation({ restore: unarchiveSession, open: async (id) => {
761
+ await openConversation(id);
762
+ if (navigationActive.current) close?.();
763
+ } }, session.id, restore, () => navigationActive.current);
764
+ } catch (reason) {
765
+ if (navigationActive.current) setError(formatArchiveNavigationError(reason, t));
766
+ } finally {
767
+ navigationPending.current = false;
768
+ if (navigationActive.current) setBusy(false);
769
+ }
770
+ };
771
+ (0, react.useEffect)(() => {
772
+ if (viewState) Object.assign(viewState, { query, project, sortBy });
773
+ }, [query, project, sortBy, viewState]);
682
774
  const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
683
775
  const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
684
776
  (0, react.useEffect)(() => {
@@ -814,12 +906,18 @@
814
906
  className: "dsham_settingsSearch",
815
907
  children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })]
816
908
  }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-sort-filter", value: sortBy, options: [{ value: "updated", label: t("archives.sortUpdated") }, { value: "created", label: t("archives.sortCreated") }, { value: "alphabetical", label: t("archives.sortAlphabetical") }], onChange: setSortBy, "aria-label": t("archives.sortBy") }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-project-filter", value: project, options: [{ value: "all", label: t("archives.allProjects") }, ...sortedGroups.map((group) => ({ value: group.key, label: group.title }))], onChange: setProject, "aria-label": t("archives.projectFilter") })]
817
- }), groups.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
818
- className: "dsham_settingsSelection",
819
- children: [(0, react_jsx_runtime.jsxs)("label", {
820
- className: "dsham_settingsSelectionToggle",
821
- children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, { checked: allVisibleSelected, indeterminate: selectedVisibleCount > 0 && !allVisibleSelected, disabled: busy || visibleSessionIds.length === 0, label: t("archives.selectAllFiltered"), onChange: (event) => toggleVisibleSelection(event.target.checked) }), t("archives.selectAllFiltered")]
822
- }), (0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsSelectionCount", children: t("archives.selectedCount", { n: selectedSessionIds.length }) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy || selectedSessionIds.length === 0, onClick: onSelectedUnarchive, children: t("archives.restoreSelected") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction dsham_settingsSelectionDelete", disabled: busy || selectedSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: { scope: "sessions", sessionIds: selectedSessionIds }, count: selectedSessionIds.length }), children: t("archives.deleteSelected") })]
909
+ }), groups.length > 0 && (0, react_jsx_runtime.jsx)(ArchiveSelectionToolbar, {
910
+ selectedCount: selectedSessionIds.length,
911
+ hiddenCount: selectedSessionIds.length - selectedVisibleCount,
912
+ allVisibleSelected,
913
+ selectedVisibleCount,
914
+ visibleCount: visibleSessionIds.length,
915
+ busy,
916
+ t,
917
+ onToggle: toggleVisibleSelection,
918
+ onClear: () => setSelectedSessionIds([]),
919
+ onRestore: onSelectedUnarchive,
920
+ onDelete: () => setDeleteTarget({ kind: "batch", target: { scope: "sessions", sessionIds: selectedSessionIds }, count: selectedSessionIds.length })
823
921
  }), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.empty") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => {
824
922
  const target = archivedBatchTargetForGroup(group.key);
825
923
  const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
@@ -834,7 +932,7 @@
834
932
  className: "dsham_settingsRow",
835
933
  children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, { checked: selectedSessionIdSet.has(session.id), disabled: busy, label: t("archives.selectSession", { name: displayTitle(session, t) }), onChange: (event) => toggleSessionSelection(session.id, event.target.checked) }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsContent", children: [(0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsTitle", children: displayTitle(session, t) }), (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsMeta", children: archiveTimeLabel(session.updatedAt, t) })] }), (0, react_jsx_runtime.jsxs)("div", {
836
934
  className: "dsham_settingsActions",
837
- children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
935
+ children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => viewConversation(session), children: t("archives.viewConversation") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => viewConversation(session, true), children: t("archives.restoreOpen") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
838
936
  })]
839
937
  }, session.id))
840
938
  })]
@@ -3077,7 +3175,7 @@
3077
3175
  const ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS = ".dsham_settingsTitleRow{display:flex;align-items:center;gap:8px 12px;min-width:0;flex-wrap:wrap}.dsham_settingsLinks{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsham_settingsExternalLink{display:inline-flex;align-items:center;gap:5px;min-height:28px;padding:0 8px;color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dsham_settingsExternalLink:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsExternalLink:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_settingsExternalLink svg{flex:none}@media(max-width:720px){.dsham_settingsTitleRow{flex-wrap:wrap}}";
3078
3176
  const ARCHIVE_SETTINGS_DELETE_CONFIRM_CSS = ".dsham_settingsDeleteConfirm{color:var(--dsw-alias-state-error-primary)!important;background:transparent!important;border-color:var(--dsw-alias-state-error-primary)!important}.dsham_settingsDeleteConfirm:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)!important}.dsham_settingsDeleteConfirm:focus-visible{outline:2px solid var(--dsw-alias-state-error-secondary);outline-offset:2px}.dsham_settingsDeleteConfirm:disabled{cursor:not-allowed;opacity:.5}";
3079
3177
  const ARCHIVE_WORKSPACE_CONFIRM_CSS = ".dsham_archiveWorkspaceConfirm{color:var(--dsw-alias-state-error-primary)!important;background:transparent!important;border-color:var(--dsw-alias-state-error-primary)!important}.dsham_archiveWorkspaceConfirm:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)!important}.dsham_archiveWorkspaceConfirm:focus-visible{outline:2px solid var(--dsw-alias-state-error-secondary);outline-offset:2px}.dsham_archiveWorkspaceConfirm:disabled{cursor:not-allowed;opacity:.5}";
3080
- const ARCHIVE_SETTINGS_SELECTION_CSS = ".dsham_settingsSelection{display:flex;align-items:center;gap:10px;min-height:40px;margin:-8px 0 16px;padding:4px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-button-elevated-fill))}.dsham_settingsSelectionToggle{display:inline-flex;align-items:center;gap:8px;min-width:0;color:var(--dsw-alias-label-primary);font-size:13px;cursor:pointer}.dsham_settingsCheckbox{width:16px;height:16px;margin:0;accent-color:#3b82f6}.dsham_settingsSelectionCount{margin-right:auto;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsSelectionAction{min-height:28px;padding:0 10px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;cursor:pointer;font:inherit;font-size:12px;font-weight:500}.dsham_settingsSelectionAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsSelectionDelete{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.dsham_settingsSelectionAction:disabled{cursor:not-allowed;opacity:.5}@media(max-width:720px){.dsham_settingsSelection{flex-wrap:wrap}.dsham_settingsSelectionCount{margin-right:0}.dsham_settingsSelectionAction{margin-left:auto}.dsham_settingsSelectionAction+.dsham_settingsSelectionAction{margin-left:-2px}}";
3178
+ const ARCHIVE_SETTINGS_SELECTION_CSS = ".dsham_settingsSelection{margin:-8px 0 16px;padding:4px 0 12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_settingsSelectionMain{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;min-height:36px}.dsham_settingsSelectionSummary{display:flex;flex-wrap:wrap;align-items:center;gap:12px;min-width:0;max-width:100%}.dsham_settingsSelectionToggle{display:inline-flex;flex:none;white-space:nowrap;align-items:center;gap:12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;cursor:pointer}.dsham_settingsCheckbox{flex:none;width:16px;height:16px;margin:0;accent-color:var(--dsw-alias-state-business-primary,#3b82f6)}.dsham_settingsSelectionClear{flex:none;min-height:28px;padding:0 0 0 12px;border:0;border-left:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-state-business-primary,#3b82f6);font:inherit;font-size:13px;white-space:nowrap;cursor:pointer}.dsham_settingsSelectionClear:hover:not(:disabled){text-decoration:underline}.dsham_settingsSelectionActions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;max-width:100%;margin-left:auto}.dsham_settingsSelectionAction{flex:none;white-space:nowrap;min-height:32px;padding:0 14px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsSelectionAction:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsSelectionDelete{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.dsham_settingsSelectionScope{margin:8px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow-wrap:anywhere}.dsham_settingsSelectionAction:disabled,.dsham_settingsSelectionClear:disabled{cursor:not-allowed;opacity:.5}.dsham_settingsSelectionAction:focus-visible,.dsham_settingsSelectionClear:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3b82f6);outline-offset:3px}";
3081
3179
  const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:0 auto!important}@media(max-width:720px){.dsham_settings{margin:0 auto!important}}";
3082
3180
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
3083
3181
  const tag = document.createElement("style");
@@ -3217,6 +3315,32 @@
3217
3315
  }) : null]
3218
3316
  });
3219
3317
  }
3318
+ function ArchiveSelectionToolbar({ selectedCount, hiddenCount, allVisibleSelected, selectedVisibleCount, visibleCount, busy, t, onToggle, onClear, onRestore, onDelete }) {
3319
+ const hasSelection = selectedCount > 0;
3320
+ return (0, react_jsx_runtime.jsxs)("div", {
3321
+ className: "dsham_settingsSelection",
3322
+ children: [(0, react_jsx_runtime.jsxs)("div", {
3323
+ className: "dsham_settingsSelectionMain",
3324
+ children: [(0, react_jsx_runtime.jsxs)("div", {
3325
+ className: "dsham_settingsSelectionSummary",
3326
+ children: [(0, react_jsx_runtime.jsxs)("label", {
3327
+ className: "dsham_settingsSelectionToggle",
3328
+ title: t("archives.selectAllFiltered"),
3329
+ children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, {
3330
+ checked: allVisibleSelected,
3331
+ indeterminate: selectedVisibleCount > 0 && !allVisibleSelected,
3332
+ disabled: busy || visibleCount === 0,
3333
+ label: t("archives.selectAllFiltered"),
3334
+ onChange: (event) => onToggle(event.target.checked)
3335
+ }), (0, react_jsx_runtime.jsx)("span", { "aria-live": "polite", children: hasSelection ? t("archives.selectedCount", { n: selectedCount }) : t("archives.selectAllFiltered") })]
3336
+ }), hasSelection && (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionClear", disabled: busy, "aria-label": t("archives.clearSelection"), onClick: onClear, children: t("archives.clearSelectionShort") })]
3337
+ }), hasSelection && (0, react_jsx_runtime.jsxs)("div", {
3338
+ className: "dsham_settingsSelectionActions",
3339
+ children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy, "aria-label": t("archives.restoreSelected"), onClick: onRestore, children: t("archives.restoreSelectionShort") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction dsham_settingsSelectionDelete", disabled: busy, "aria-label": t("archives.deleteSelected"), onClick: onDelete, children: t("archives.deleteSelectionShort") })]
3340
+ })]
3341
+ }), hiddenCount > 0 && (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsSelectionScope", role: "status", children: t("archives.selectionHiddenHint", { hidden: hiddenCount }) })]
3342
+ });
3343
+ }
3220
3344
  function ArchiveSelectionCheckbox({ checked, indeterminate = false, disabled = false, label, onChange }) {
3221
3345
  const inputRef = (0, react.useRef)(null);
3222
3346
  (0, react.useEffect)(() => {
@@ -3355,172 +3479,17 @@
3355
3479
  return compareText(left.title, right.title) || compareText(left.key, right.key);
3356
3480
  });
3357
3481
  }
3358
- function ArchivedSessionsSection({ sessionStore, workspaceStore, unarchiveSession, deleteSession, unarchiveSessions, deleteArchivedSessions, archivedSessionMetadata, t }) {
3359
- const sessions = (0, react.useSyncExternalStore)(sessionStore.subscribe, sessionStore.getSnapshot);
3360
- const workspaceState = (0, react.useSyncExternalStore)(workspaceStore.subscribe, workspaceStore.getSnapshot);
3361
- const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
3362
- const [busy, setBusy] = (0, react.useState)(false);
3363
- const [error, setError] = (0, react.useState)(null);
3364
- const [notice, setNotice] = (0, react.useState)(null);
3365
- const [query, setQuery] = (0, react.useState)("");
3366
- const [project, setProject] = (0, react.useState)("all");
3367
- const [sortBy, setSortBy] = (0, react.useState)("updated");
3368
- const [createdAtById, setCreatedAtById] = (0, react.useState)({});
3369
- const [unarchivingSessionIds, setUnarchivingSessionIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
3370
- const unarchivingSessionIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
3371
- const [selectedSessionIds, setSelectedSessionIds] = (0, react.useState)([]);
3372
- const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
3373
- const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
3374
- (0, react.useEffect)(() => {
3375
- let cancelled = false;
3376
- archivedSessionMetadata().then((result) => {
3377
- if (cancelled) return;
3378
- setCreatedAtById(Object.fromEntries(result.items.map((item) => [item.sessionId, item.createdAt])));
3379
- }).catch(() => {
3380
- if (!cancelled) setCreatedAtById({});
3381
- });
3382
- return () => {
3383
- cancelled = true;
3384
- };
3385
- }, [archivedSessionMetadata, workspaceState.archivedSessionIds]);
3386
- (0, react.useEffect)(() => {
3387
- if (project === "all") return;
3388
- if (groups.some((group) => group.key === project)) return;
3389
- setProject("all");
3390
- }, [groups, project]);
3391
- const filteredGroups = (0, react.useMemo)(() => {
3392
- const normalizedQuery = query.trim().toLocaleLowerCase();
3393
- return sortedGroups.filter((group) => project === "all" || project === group.key).map((group) => ({
3394
- ...group,
3395
- sessions: group.sessions.filter((session) => normalizedQuery === "" || displayTitle(session, t).toLocaleLowerCase().includes(normalizedQuery))
3396
- })).filter((group) => group.sessions.length > 0);
3397
- }, [sortedGroups, project, query, t]);
3398
- const visibleSessionIds = (0, react.useMemo)(() => archivedSessionIdsInGroups(filteredGroups), [filteredGroups]);
3399
- const selectedSessionIdSet = (0, react.useMemo)(() => new Set(selectedSessionIds), [selectedSessionIds]);
3400
- const selectedVisibleCount = visibleSessionIds.filter((sessionId) => selectedSessionIdSet.has(sessionId)).length;
3401
- const allVisibleSelected = visibleSessionIds.length > 0 && selectedVisibleCount === visibleSessionIds.length;
3402
- const allBatchTarget = { scope: "all" };
3403
- const allBatchSessionIds = (0, react.useMemo)(() => deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, allBatchTarget), [workspaceState.archivedSessionIds, workspaceState.items]);
3404
- (0, react.useEffect)(() => {
3405
- setSelectedSessionIds((current) => pruneArchivedSelection(current, workspaceState.archivedSessionIds));
3406
- }, [workspaceState.archivedSessionIds]);
3407
- const toggleSessionSelection = (sessionId, checked) => {
3408
- setSelectedSessionIds((current) => toggleArchivedSelection(current, [sessionId], checked));
3409
- };
3410
- const toggleVisibleSelection = (checked) => {
3411
- setSelectedSessionIds((current) => toggleArchivedSelection(current, visibleSessionIds, checked));
3412
- };
3413
- const onUnarchive = (sessionId) => {
3414
- if (busy || unarchivingSessionIdsRef.current.has(sessionId)) return;
3415
- unarchivingSessionIdsRef.current.add(sessionId);
3416
- setUnarchivingSessionIds(new Set(unarchivingSessionIdsRef.current));
3417
- setError(null);
3418
- setNotice(null);
3419
- unarchiveSession(sessionId).catch((reason) => {
3420
- setError(formatUnarchiveError(reason, t));
3421
- }).finally(() => {
3422
- unarchivingSessionIdsRef.current.delete(sessionId);
3423
- setUnarchivingSessionIds(new Set(unarchivingSessionIdsRef.current));
3424
- });
3425
- };
3426
- const onBatchUnarchive = async (target) => {
3427
- if (busy) return;
3428
- setBusy(true);
3429
- setError(null);
3430
- setNotice(null);
3431
- try {
3432
- const result = await unarchiveSessions(target);
3433
- setNotice(t("archives.restoreSuccess", { n: result.unarchivedSessionIds.length }));
3434
- return result;
3435
- } catch (reason) {
3436
- setError(t("archives.restoreBatchFailed", { detail: reason instanceof Error ? reason.message : String(reason) }));
3437
- } finally {
3438
- setBusy(false);
3439
- }
3440
- };
3441
- const onSelectedUnarchive = async () => {
3442
- if (selectedSessionIds.length === 0) return;
3443
- const result = await onBatchUnarchive({ scope: "sessions", sessionIds: selectedSessionIds });
3444
- if (result !== void 0) {
3445
- const restored = new Set(result.unarchivedSessionIds);
3446
- setSelectedSessionIds((current) => current.filter((sessionId) => !restored.has(sessionId)));
3447
- }
3448
- };
3449
- const closeDelete = () => {
3450
- if (!busy) setDeleteTarget(null);
3451
- };
3452
- (0, react.useEffect)(() => {
3453
- if (deleteTarget === null) return;
3454
- const onKeyDown = (event) => {
3455
- if (event.key !== "Escape") return;
3456
- event.preventDefault();
3457
- event.stopPropagation();
3458
- if (typeof event.stopImmediatePropagation === "function") event.stopImmediatePropagation();
3459
- if (!busy) setDeleteTarget(null);
3460
- };
3461
- window.addEventListener("keydown", onKeyDown, true);
3462
- return () => window.removeEventListener("keydown", onKeyDown, true);
3463
- }, [deleteTarget, busy]);
3464
- const confirmDelete = async () => {
3465
- if (busy || deleteTarget === null) return;
3466
- setBusy(true);
3467
- setError(null);
3468
- setNotice(null);
3469
- try {
3470
- if (deleteTarget.kind === "batch") {
3471
- const result = await deleteArchivedSessions(deleteTarget.target);
3472
- const feedback = archivedDeleteFeedback(result, t);
3473
- if (feedback.kind === "error") setError(feedback.message);
3474
- else setNotice(feedback.message);
3475
- if (deleteTarget.target.scope === "sessions") {
3476
- const completed = /* @__PURE__ */ new Set([...result.deletedSessionIds, ...result.skippedSessionIds]);
3477
- setSelectedSessionIds((current) => current.filter((sessionId) => !completed.has(sessionId)));
3478
- }
3479
- } else {
3480
- await deleteSession(deleteTarget.session.id);
3481
- }
3482
- setDeleteTarget(null);
3483
- } catch (reason) {
3484
- setError(formatDeleteError(reason, t));
3485
- } finally {
3486
- setBusy(false);
3487
- }
3488
- };
3489
- const batchScope = deleteTarget?.kind === "batch" ? deleteTarget.target.scope : null;
3490
- const deleteDialogTitle = batchScope === "all" ? t("archives.deleteAllTitle") : batchScope === "ungrouped" ? t("archives.deleteUngroupedTitle") : batchScope === "workspace" ? t("archives.deleteProjectTitle", { name: deleteTarget.title }) : batchScope === "sessions" ? t("archives.deleteSelectedTitle") : t("deleteSession.title");
3491
- const deleteDialogDescription = deleteTarget === null ? void 0 : batchScope === "all" ? t("archives.deleteAllDesc", { n: deleteTarget.count }) : batchScope === "ungrouped" ? t("archives.deleteUngroupedDesc", { n: deleteTarget.count }) : batchScope === "workspace" ? t("archives.deleteProjectDesc", { name: deleteTarget.title, n: deleteTarget.count }) : batchScope === "sessions" ? t("archives.deleteSelectedDesc", { n: deleteTarget.count }) : t("deleteSession.desc", { name: displayTitle(deleteTarget.session, t) });
3492
- const deleteConfirmLabel = batchScope === "all" ? t("archives.deleteAll") : batchScope === "ungrouped" ? t("archives.deleteUngroupedConfirm") : batchScope === "workspace" ? t("archives.deleteProjectConfirm") : batchScope === "sessions" ? t("archives.deleteSelectedConfirm") : t("deleteSession.title");
3493
- return (0, react_jsx_runtime.jsxs)("section", {
3494
- className: "dsham_settings",
3495
- "aria-label": t("archives.title"),
3496
- children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS + ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS }), (0, react_jsx_runtime.jsxs)("header", { className: "dsham_settingsHeader", children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsTitleRow", children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsLinks", children: [(0, react_jsx_runtime.jsxs)("a", { className: "dsham_settingsExternalLink", href: "https://github.com/MichengAI/dsh-archive-manager", target: "_blank", rel: "noreferrer", "aria-label": t("archives.viewProject"), children: [(0, react_jsx_runtime.jsx)(GithubMark16, {}), t("archives.viewProject")] }), (0, react_jsx_runtime.jsxs)("a", { className: "dsham_settingsExternalLink", href: "https://github.com/MichengAI/dsh-archive-manager/issues", target: "_blank", rel: "noreferrer", "aria-label": t("archives.feedback"), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, {}), t("archives.feedback")] })] })] }), (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsIntro", children: t("archives.description") })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsHeaderActions", children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsRestoreAll", disabled: busy || allBatchSessionIds.length === 0, onClick: () => onBatchUnarchive(allBatchTarget), children: t("archives.restoreAll") }), (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "dsham_settingsDanger", disabled: busy || allBatchSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: allBatchTarget, title: t("archives.allProjects"), count: allBatchSessionIds.length }), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}), t("archives.deleteAll")] })] })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsToolbar", children: [(0, react_jsx_runtime.jsxs)("label", { className: "dsham_settingsSearch", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })] }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-sort-filter", value: sortBy, options: [{ value: "updated", label: t("archives.sortUpdated") }, { value: "created", label: t("archives.sortCreated") }, { value: "alphabetical", label: t("archives.sortAlphabetical") }], onChange: setSortBy, "aria-label": t("archives.sortBy") }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-project-filter", value: project, options: [{ value: "all", label: t("archives.allProjects") }, ...sortedGroups.map((group) => ({ value: group.key, label: group.title }))], onChange: setProject, "aria-label": t("archives.projectFilter") })] }), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.empty") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => {
3497
- const target = archivedBatchTargetForGroup(group.key);
3498
- const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
3499
- return (0, react_jsx_runtime.jsxs)("section", {
3500
- className: "dsham_settingsGroup",
3501
- children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsGroupHeading", children: [(0, react_jsx_runtime.jsxs)("h3", { className: "dsham_settingsGroupTitle", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, {}), group.title] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsGroupMeta", children: [(0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsCount", children: t("archives.sessionCount", { n: count }) }), (0, react_jsx_runtime.jsx)(ArchivedGroupActions, { group, busy, onRestore: () => onBatchUnarchive(target), onDelete: () => setDeleteTarget({ kind: "batch", target, title: group.title, count }), t })] })] }), (0, react_jsx_runtime.jsx)("div", {
3502
- className: "dsham_settingsList",
3503
- children: group.sessions.map((session) => (0, react_jsx_runtime.jsxs)("article", {
3504
- className: "dsham_settingsRow",
3505
- children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsContent", children: [(0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsTitle", children: displayTitle(session, t) }), (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsMeta", children: archiveTimeLabel(session.updatedAt, t) })] }), (0, react_jsx_runtime.jsxs)("div", {
3506
- className: "dsham_settingsActions",
3507
- children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
3508
- })]
3509
- }, session.id))
3510
- })]
3511
- }, group.key);
3512
- }), error !== null && (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsError", role: "alert", children: error }), notice !== null && (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsStatus", role: "status", children: notice }), (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3513
- open: deleteTarget !== null,
3514
- onClose: closeDelete,
3515
- closeLabel: t("close"),
3516
- title: deleteDialogTitle,
3517
- ...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
3518
- footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: closeDelete, children: t("cancel") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", className: "dsham_settingsDeleteConfirm", disabled: busy, onClick: confirmDelete, children: deleteConfirmLabel })] }),
3519
- children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
3520
- })]
3521
- });
3522
- }
3523
3482
  const zh = {
3483
+ "archives.viewConversation": "\u67E5\u770B\u5BF9\u8BDD",
3484
+ "archives.navigationUnavailable": "\u5F53\u524D\u5BBF\u4E3B\u65E0\u6CD5\u4FDD\u6301\u5F52\u6863\u5BF9\u8BDD\uFF0C\u8BF7\u4F7F\u7528\u201C\u6062\u590D\u5E76\u6253\u5F00\u201D\u3002",
3485
+ "archives.sessionNotRetained": "\u5BBF\u4E3B\u672A\u4FDD\u7559\u76EE\u6807\u4F1A\u8BDD\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u6216\u6062\u590D\u540E\u518D\u8BD5\u3002",
3486
+ "archives.restoreOpen": "\u6062\u590D\u5E76\u6253\u5F00",
3487
+ "archives.selectedCount": "\u5DF2\u9009 {n} \u6761",
3488
+ "archives.selectionHiddenHint": "\u542B\u5F53\u524D\u672A\u663E\u793A\u7684 {hidden} \u6761\uFF0C\u4E5F\u5C06\u53C2\u4E0E\u64CD\u4F5C",
3489
+ "archives.clearSelectionShort": "\u6E05\u7A7A",
3490
+ "archives.restoreSelectionShort": "\u6062\u590D",
3491
+ "archives.deleteSelectionShort": "\u5220\u9664",
3492
+ "archives.clearSelection": "\u6E05\u7A7A\u9009\u62E9",
3524
3493
  "group.ungrouped": "\u672A\u5206\u7EC4",
3525
3494
  "session.new": "\u65B0\u4F1A\u8BDD",
3526
3495
  "section.workspaces": "\u5DE5\u4F5C\u533A",
@@ -3535,7 +3504,6 @@
3535
3504
  "sessions.expand": "\u5C55\u5F00\u5176\u4F59 {n} \u4E2A\u4F1A\u8BDD",
3536
3505
  "sessions.collapse": "\u6536\u8D77",
3537
3506
  "empty.none": "\u6682\u65E0\u4F1A\u8BDD",
3538
- "empty.noMatches": "\u65E0\u5339\u914D\u7ED3\u679C",
3539
3507
  "workspace.add": "\u6DFB\u52A0\u5DE5\u4F5C\u533A",
3540
3508
  "search.sessions.aria": "\u641C\u7D22\u4F1A\u8BDD",
3541
3509
  "search.placeholder": "\u641C\u7D22\u4F1A\u8BDD\u2026",
@@ -3566,7 +3534,6 @@
3566
3534
  "archives.sessionCount": "{n} \u4E2A\u804A\u5929",
3567
3535
  "archives.selectAllFiltered": "\u5168\u9009\u5F53\u524D\u7B5B\u9009\u7ED3\u679C",
3568
3536
  "archives.selectSession": "\u9009\u62E9\u4F1A\u8BDD\u201C{name}\u201D",
3569
- "archives.selectedCount": "\u5DF2\u9009 {n} \u4E2A",
3570
3537
  "archives.restoreSelected": "\u6062\u590D\u6240\u9009",
3571
3538
  "archives.deleteSelected": "\u5220\u9664\u6240\u9009",
3572
3539
  "archives.timestamp": "{date}\uFF0C{time}",
@@ -3585,7 +3552,6 @@
3585
3552
  "archives.deleteSelectedTitle": "\u5220\u9664\u6240\u9009\u5DF2\u5F52\u6863\u804A\u5929",
3586
3553
  "archives.deleteSelectedDesc": "\u5C06\u6C38\u4E45\u5220\u9664\u6240\u9009\u7684 {n} \u4E2A\u5DF2\u5F52\u6863\u804A\u5929\u53CA\u5176\u5B50\u4EE3\u7406\uFF08\u542B\u6B63\u5728\u8FD0\u884C\u7684\uFF09\u548C\u8BB0\u5F55\u3002\u5176\u4ED6\u804A\u5929\u4E0D\u4F1A\u53D7\u5F71\u54CD\uFF0C\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
3587
3554
  "archives.deleteSelectedConfirm": "\u5220\u9664\u6240\u9009\u804A\u5929",
3588
- "archives.deleteAllPending": "\u6B63\u5728\u5220\u9664\u5DF2\u5F52\u6863\u804A\u5929\u2026",
3589
3555
  "archives.deleteProjectTitle": "\u5220\u9664\u201C{name}\u201D\u4E2D\u7684\u5DF2\u5F52\u6863\u804A\u5929",
3590
3556
  "archives.deleteProjectDesc": "\u5C06\u6C38\u4E45\u5220\u9664\u201C{name}\u201D\u4E2D\u7684 {n} \u4E2A\u5DF2\u5F52\u6863\u804A\u5929\u53CA\u5176\u5B50\u4EE3\u7406\u548C\u8BB0\u5F55\u3002\u9879\u76EE\u76EE\u5F55\u548C\u672A\u5F52\u6863\u804A\u5929\u4E0D\u4F1A\u53D7\u5F71\u54CD\uFF0C\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
3591
3557
  "archives.deleteProjectConfirm": "\u5220\u9664\u8BE5\u9879\u76EE\u7684\u5168\u90E8\u804A\u5929",
@@ -3626,8 +3592,6 @@
3626
3592
  "archiveWorkspace.desc": "\u8FD9\u4F1A\u5C06\u201C{name}\u201D\u4E2D\u7684\u804A\u5929\u5F52\u6863\u3002\u4E4B\u540E\u4F60\u53EF\u4EE5\u5728\u5DF2\u5F52\u6863\u7684\u804A\u5929\u4E2D\u627E\u5230\u5B83\u4EEC\u3002",
3627
3593
  "archiveWorkspace.confirm": "\u5168\u90E8\u5F52\u6863",
3628
3594
  "archiveWorkspace.pending": "\u6B63\u5728\u5F52\u6863\u804A\u5929\u2026",
3629
- "sessions.count.one": "{n} \u4E2A\u4F1A\u8BDD",
3630
- "sessions.count.other": "{n} \u4E2A\u4F1A\u8BDD",
3631
3595
  "actions.workspace.aria": "\u5DE5\u4F5C\u533A\u201C{name}\u201D\u7684\u64CD\u4F5C",
3632
3596
  "actions.session.aria": "\u4F1A\u8BDD\u201C{name}\u201D\u7684\u64CD\u4F5C",
3633
3597
  "actions.newSession.aria": "\u5728\u201C{name}\u201D\u4E2D\u65B0\u5EFA\u4F1A\u8BDD",
@@ -3651,6 +3615,16 @@
3651
3615
  "time.ago": "{t}\u524D"
3652
3616
  };
3653
3617
  const en = {
3618
+ "archives.viewConversation": "View conversation",
3619
+ "archives.navigationUnavailable": "This host cannot keep the conversation archived while opening it. Use \u201CRestore and open\u201D instead.",
3620
+ "archives.sessionNotRetained": "The host did not keep the requested conversation open. Try again, or restore it before opening.",
3621
+ "archives.restoreOpen": "Restore and open",
3622
+ "archives.selectedCount": "{n} selected",
3623
+ "archives.selectionHiddenHint": "Includes {hidden} hidden by the current filter; actions apply to them too.",
3624
+ "archives.clearSelectionShort": "Clear",
3625
+ "archives.restoreSelectionShort": "Restore",
3626
+ "archives.deleteSelectionShort": "Delete",
3627
+ "archives.clearSelection": "Clear selection",
3654
3628
  "group.ungrouped": "Ungrouped",
3655
3629
  "session.new": "New Session",
3656
3630
  "section.workspaces": "Workspaces",
@@ -3665,7 +3639,6 @@
3665
3639
  "sessions.expand": "Show {n} more sessions",
3666
3640
  "sessions.collapse": "Show less",
3667
3641
  "empty.none": "No sessions yet",
3668
- "empty.noMatches": "No matches",
3669
3642
  "workspace.add": "Add workspace",
3670
3643
  "search.sessions.aria": "Search sessions",
3671
3644
  "search.placeholder": "Search sessions...",
@@ -3696,7 +3669,6 @@
3696
3669
  "archives.sessionCount": "{n} chats",
3697
3670
  "archives.selectAllFiltered": "Select all results",
3698
3671
  "archives.selectSession": "Select chat {name}",
3699
- "archives.selectedCount": "{n} selected",
3700
3672
  "archives.restoreSelected": "Restore selected",
3701
3673
  "archives.deleteSelected": "Delete selected",
3702
3674
  "archives.timestamp": "{date}, {time}",
@@ -3715,7 +3687,6 @@
3715
3687
  "archives.deleteSelectedTitle": "Delete selected archived chats",
3716
3688
  "archives.deleteSelectedDesc": "This permanently deletes the selected {n} archived chats, their child agents (including any that are still running), and their records. Other chats are not affected. This cannot be undone.",
3717
3689
  "archives.deleteSelectedConfirm": "Delete selected chats",
3718
- "archives.deleteAllPending": "Deleting archived chats\u2026",
3719
3690
  "archives.deleteProjectTitle": "Delete archived chats in {name}",
3720
3691
  "archives.deleteProjectDesc": "This permanently deletes the {n} archived chats in {name}, their child agents, and their records. The project directory and unarchived chats are not affected. This cannot be undone.",
3721
3692
  "archives.deleteProjectConfirm": "Delete all project chats",
@@ -3756,8 +3727,6 @@
3756
3727
  "archiveWorkspace.desc": "This archives the chats in \u201C{name}\u201D. You can find them later in Archived chats.",
3757
3728
  "archiveWorkspace.confirm": "Archive all",
3758
3729
  "archiveWorkspace.pending": "Archiving chats\u2026",
3759
- "sessions.count.one": "{n} session",
3760
- "sessions.count.other": "{n} sessions",
3761
3730
  "actions.workspace.aria": "Workspace actions for {name}",
3762
3731
  "actions.session.aria": "Session actions for {name}",
3763
3732
  "actions.newSession.aria": "New session in {name}",
@@ -3828,6 +3797,15 @@
3828
3797
  createIcon: createPluginUpdateIcon
3829
3798
  }), "dsh-archive-manager: plugin update ui");
3830
3799
  const uiWorkspaceAt = () => ctx.get("uiWorkspace");
3800
+ const archiveViewState = {};
3801
+ let archiveNavigation;
3802
+ ctx.effect(() => {
3803
+ archiveNavigation = allowArchivedNavigation(uiWorkspaceAt(), ctx.sessions, ctx.workspaces, {
3804
+ onOpened: () => ctx.get("layout")?.selectPanel(null)
3805
+ });
3806
+ return () => archiveNavigation.dispose();
3807
+ }, "archive-manager: explicit archived navigation");
3808
+ const openConversation = (sessionId) => archiveNavigation.open(sessionId);
3831
3809
  const searchSessions = async (query, signal) => {
3832
3810
  const result = await ctx.sessions.search(query, signal);
3833
3811
  if (!result.ok) throw new Error(result.error.message);
@@ -3970,11 +3948,16 @@
3970
3948
  unarchiveSessions,
3971
3949
  deleteArchivedSessions,
3972
3950
  archivedSessionMetadata,
3951
+ openConversation,
3952
+ viewState: archiveViewState,
3973
3953
  t: ctx.locale.bind(NS)
3974
3954
  })
3975
- }, ArchivedSessionsSectionPrototype));
3955
+ }, ArchivedSessionsSection));
3976
3956
  }
3977
3957
  exports.__test = {
3958
+ ArchiveSelectionToolbar,
3959
+ ArchivedSessionsSection,
3960
+ zh,
3978
3961
  displayTitle,
3979
3962
  sessionVisible,
3980
3963
  indexSubagentDescendants,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "NPM-installable DSH Web plugin for managing archived sessions.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",