@michengai/dsh-archive-manager 0.1.39 → 0.1.40
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 +6 -0
- package/CHANGELOG.zh-CN.md +6 -0
- package/README.md +62 -123
- package/README.zh-CN.md +64 -125
- package/lib/archive-experience.js +68 -0
- package/lib/client.js +124 -182
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
Published release notes are retained below; new versions are added without removing earlier entries.
|
|
6
6
|
|
|
7
|
+
## 0.1.40 - 2026-09-13
|
|
8
|
+
|
|
9
|
+
- Open archived sessions in the native DSH conversation view and continue chatting while keeping them archived, or restore and open them.
|
|
10
|
+
- 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.
|
|
11
|
+
- Localize archive navigation errors and avoid unnecessary warnings when older hosts load or open archives successfully.
|
|
12
|
+
|
|
7
13
|
## 0.1.39 - 2026-09-11
|
|
8
14
|
|
|
9
15
|
- Preserve the Codex UI workspace list, styling, and dedicated interactions when both plugins are installed, while keeping archive management available.
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
以下发布说明会持续保留;新增版本时不再删除较早记录。
|
|
6
6
|
|
|
7
|
+
## 0.1.40 - 2026-09-13
|
|
8
|
+
|
|
9
|
+
- 支持直接打开归档会话,在 DSH 原生页面查看完整对话并保持归档继续聊天,也可恢复后打开。
|
|
10
|
+
- 修复查看归档后设置遮罩未关闭的问题:打开成功后关闭设置,失败时保留设置并显示错误;宿主导航适配不可用时可恢复后打开。
|
|
11
|
+
- 归档打开失败提示随界面语言显示,旧宿主正常加载和打开归档时不再产生多余警告。
|
|
12
|
+
|
|
7
13
|
## 0.1.39 - 2026-09-11
|
|
8
14
|
|
|
9
15
|
- 修复与 Codex UI 共存时抢占其工作区列表的问题,保留 Codex UI 的列表样式和专属交互,同时提供归档管理入口。
|
package/README.md
CHANGED
|
@@ -14,196 +14,135 @@
|
|
|
14
14
|
[](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
|
|
15
15
|
[](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
|
|
16
16
|
[](https://github.com/MichengAI/dsh-archive-manager)
|
|
17
|
-
[](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
|
-
##
|
|
21
|
+
## What you can do
|
|
23
22
|
|
|
24
|
-
Put inactive conversations away and find them again when needed
|
|
23
|
+
Put inactive conversations away and find them again when needed, keeping everyday task lists tidy.
|
|
25
24
|
|
|
26
|
-
- **
|
|
27
|
-
- **Find past work**: search titles, filter by project, and sort by time or title
|
|
28
|
-
- **
|
|
29
|
-
- **Clean up records**: permanently delete
|
|
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
|
-
|
|
32
|
+
Archive a chat from the sidebar session menu:
|
|
34
33
|
|
|
35
34
|

|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
Find, restore, and clean up chats in **Settings → Archived sessions**:
|
|
38
37
|
|
|
39
38
|

|
|
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
|
-
-
|
|
59
|
-
|
|
60
|
-
-
|
|
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
|
-
|
|
48
|
+
Examples use the `web` profile. Replace it with the profile you actually use.
|
|
69
49
|
|
|
70
|
-
|
|
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.
|
|
50
|
+
### Ask an agent to install it
|
|
74
51
|
|
|
75
|
-
|
|
76
|
-
|
|
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 |
|
|
84
|
-
|
|
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
|
|
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
|
|
58
|
+
### Install manually
|
|
98
59
|
|
|
99
|
-
Run
|
|
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
|
-
|
|
69
|
+
Restart DSH Web, then hard-refresh your browser with `Ctrl+Shift+R`. Open **Settings → Archived sessions** to get started.
|
|
109
70
|
|
|
110
|
-
|
|
71
|
+
## Usage
|
|
111
72
|
|
|
112
|
-
|
|
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
|
-
|
|
82
|
+
Selections persist when filters change. Check the hidden selection count before applying bulk actions, or clear your selection first.
|
|
115
83
|
|
|
116
|
-
|
|
84
|
+
### View and continue archived conversations
|
|
117
85
|
|
|
118
|
-
|
|
86
|
+
Available starting with `0.1.40`:
|
|
119
87
|
|
|
120
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
## Usage
|
|
127
|
-
|
|
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.**
|
|
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.
|
|
134
94
|
|
|
135
|
-
|
|
95
|
+
## FAQ
|
|
136
96
|
|
|
137
|
-
|
|
97
|
+
### Why is the entry missing after installation?
|
|
138
98
|
|
|
139
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
+
### How is archiving different from deletion?
|
|
170
111
|
|
|
171
|
-
|
|
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
|
-
|
|
114
|
+
### Can I use it with Codex UI?
|
|
178
115
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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.
|
|
119
|
+
|
|
120
|
+
## Install from source
|
|
186
121
|
|
|
187
|
-
|
|
122
|
+
<details>
|
|
123
|
+
<summary>Expand for development or testing unreleased changes</summary>
|
|
188
124
|
|
|
189
|
-
|
|
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
|
|
195
|
-
```
|
|
196
130
|
|
|
197
|
-
|
|
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 .
|
|
136
|
+
```
|
|
198
137
|
|
|
199
|
-
|
|
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.
|
|
200
139
|
|
|
201
|
-
|
|
140
|
+
</details>
|
|
202
141
|
|
|
203
|
-
|
|
142
|
+
## Related projects
|
|
204
143
|
|
|
205
|
-
|
|
144
|
+
[DSH Codex UI](https://github.com/MichengAI/dsh-codex-ui) provides project and conversation management. For a desktop workbench, see [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop).
|
|
206
145
|
|
|
207
|
-
|
|
146
|
+
## License
|
|
208
147
|
|
|
209
|
-
|
|
148
|
+
Licensed under [Apache License 2.0](LICENSE).
|
package/README.zh-CN.md
CHANGED
|
@@ -14,196 +14,135 @@
|
|
|
14
14
|
[](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
|
|
15
15
|
[](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
|
|
16
16
|
[](https://github.com/MichengAI/dsh-archive-manager)
|
|
17
|
-
[](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
|

|
|
36
35
|
|
|
37
|
-
在「设置 →
|
|
36
|
+
在「设置 → 归档会话」集中查找、恢复和清理:
|
|
38
37
|
|
|
39
|
-
;已有 [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
|
+

|
|
55
39
|
|
|
56
40
|
## 前置条件
|
|
57
41
|
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
-
|
|
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
|
-
|
|
48
|
+
以下示例使用 `web` profile,请替换为你实际使用的 profile。
|
|
69
49
|
|
|
70
|
-
|
|
71
|
-
- `pnpm test:matrix`:先构建,再隔离安装五个宿主版本,执行完整矩阵。
|
|
72
|
-
- `pnpm test:latest`:先构建,再隔离运行最新版的 真实存储回归。
|
|
73
|
-
- `pnpm test:compat`:旧缓存迁移验证加完整矩阵。
|
|
50
|
+
### 让 Agent 帮你安装
|
|
74
51
|
|
|
75
|
-
|
|
76
|
-
|
|
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 项通过 |
|
|
84
|
-
|
|
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
|
-
请将
|
|
55
|
+
请将 @michengai/dsh-archive-manager 最新版安装到本机 DSH 的 web profile,使用官方 npm 源。安装后检查插件配置,并告诉我如何重新加载 DSH、进入归档会话管理页。
|
|
95
56
|
```
|
|
96
57
|
|
|
97
|
-
###
|
|
58
|
+
### 手动安装
|
|
98
59
|
|
|
99
|
-
|
|
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
|
-
|
|
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`,保留其余配置。
|
|
115
|
-
|
|
116
|
-
运行 `dsh --profile web --dump-config`,确认官方 `ui-workspace` 已启用,再重启 DSH Web 并硬刷新浏览器。在 DSH 0.1.2 及以上受支持版本中,禁用官方服务会让归档客户端等待 `uiWorkspace`,侧栏与归档设置页都不会挂载,可能表现为没有响应。
|
|
117
|
-
|
|
118
|
-
侧栏添加目录依赖官方 `sidebar.workspaces.directoryFlow` 插槽。源槽不存在或没有目录组件时,该入口会隐藏,不会因此记录同步错误;可使用官方首页入口。带额外子插槽的目录扩展暂不支持镜像,会输出警告。
|
|
69
|
+
安装后重启 DSH Web,并按 `Ctrl+Shift+R` 硬刷新浏览器。打开「设置 → 归档会话」即可使用。
|
|
119
70
|
|
|
120
|
-
|
|
71
|
+
## 使用
|
|
121
72
|
|
|
122
|
-
|
|
73
|
+
| 你想做什么 | 操作 |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| 归档一条会话 | 打开侧栏会话菜单,选择「归档会话」 |
|
|
76
|
+
| 归档整个工作区 | 打开工作区菜单,选择归档该工作区的会话 |
|
|
77
|
+
| 查找归档 | 打开「设置 → 归档会话」,搜索标题或按项目筛选 |
|
|
78
|
+
| 调整排列顺序 | 按更新时间、创建时间或标题排序 |
|
|
79
|
+
| 恢复一条会话 | 点击会话右侧的「取消归档」 |
|
|
80
|
+
| 批量恢复或删除 | 勾选会话后使用批量操作;也可使用项目菜单或页面顶部的全部操作 |
|
|
123
81
|
|
|
124
|
-
|
|
82
|
+
切换筛选条件会保留已选会话。批量操作前留意隐藏的已选数量,或先清空选择。
|
|
125
83
|
|
|
126
|
-
|
|
84
|
+
### 查看并继续归档对话
|
|
127
85
|
|
|
128
|
-
1.
|
|
129
|
-
2. 打开「设置 → 归档会话」,按工作区查看归档会话。
|
|
130
|
-
3. 按标题搜索,按更新时间、创建时间或标题排序,或按项目筛选列表。
|
|
131
|
-
4. 点击「取消归档」恢复单个会话,或在顶部点击「全部恢复」。
|
|
132
|
-
5. 打开项目标题右侧菜单,可恢复或删除该项目的全部已归档聊天。
|
|
133
|
-
6. 点击删除图标永久移除单个会话;删除前确认提示。**删除无法撤销。**
|
|
86
|
+
从 `0.1.40` 起支持以下操作:
|
|
134
87
|
|
|
135
|
-
|
|
88
|
+
- **查看对话**:打开 DSH 原生会话页,查看消息、附件和工具详情;可直接继续聊天,保持归档状态。
|
|
89
|
+
- **恢复并打开**:取消归档后进入原会话,继续工作。
|
|
136
90
|
|
|
137
|
-
##
|
|
91
|
+
## 更新
|
|
138
92
|
|
|
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 安装,避免手工拼接补丁配置。
|
|
93
|
+
在归档管理页标题处点击「检查更新」。支持自动更新的 DSH CLI 或 Desktop 环境可直接更新;其他环境会提供适用于当前 profile 的手动命令。也可重新执行上面的安装命令。
|
|
148
94
|
|
|
149
|
-
##
|
|
95
|
+
## 常见问题
|
|
150
96
|
|
|
151
|
-
###
|
|
97
|
+
### 安装后找不到入口?
|
|
152
98
|
|
|
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
|
-
|
|
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
|
-
|
|
108
|
+
配置中应包含 `workspace-archive-manager` 和 `ui-workspace-archive-manager`。若曾在 profile 的 `cordis.patch.yml` 中手动将官方 `ui-workspace` 设为 `disabled: true`,请移除该禁用覆盖,再重启。
|
|
168
109
|
|
|
169
|
-
|
|
110
|
+
### 归档和删除有什么区别?
|
|
170
111
|
|
|
171
|
-
|
|
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
|
-
|
|
114
|
+
### 可以和 Codex UI 一起使用吗?
|
|
178
115
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 与插件版本、复现步骤和错误信息。
|
|
119
|
+
|
|
120
|
+
## 从源码安装
|
|
186
121
|
|
|
187
|
-
|
|
122
|
+
<details>
|
|
123
|
+
<summary>开发或测试未发布改动时展开</summary>
|
|
188
124
|
|
|
189
|
-
|
|
125
|
+
在你选择的源码目录中执行以下命令。未推送的本地改动需使用已有工作副本。
|
|
190
126
|
|
|
191
127
|
```powershell
|
|
192
128
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
193
129
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
194
|
-
pnpm verify
|
|
195
|
-
```
|
|
196
130
|
|
|
197
|
-
|
|
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 .
|
|
136
|
+
```
|
|
198
137
|
|
|
199
|
-
|
|
138
|
+
完成后重启 DSH Web 并硬刷新浏览器。修改 [src](src) 中的源码,不直接编辑生成目录 `lib`;使用 `pnpm test` 验证修改,使用 `pnpm verify` 执行完整检查。
|
|
200
139
|
|
|
201
|
-
|
|
140
|
+
</details>
|
|
202
141
|
|
|
203
|
-
|
|
142
|
+
## 相关项目
|
|
204
143
|
|
|
205
|
-
|
|
144
|
+
[DSH Codex UI](https://github.com/MichengAI/dsh-codex-ui) 提供项目与会话管理界面;想使用桌面工作台,可查看 [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop)。
|
|
206
145
|
|
|
207
|
-
|
|
146
|
+
## 许可证
|
|
208
147
|
|
|
209
|
-
|
|
148
|
+
本项目采用 [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
|
|
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)(() => {
|
|
@@ -819,7 +911,7 @@
|
|
|
819
911
|
children: [(0, react_jsx_runtime.jsxs)("label", {
|
|
820
912
|
className: "dsham_settingsSelectionToggle",
|
|
821
913
|
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.
|
|
914
|
+
}), (0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsSelectionCount", children: t("archives.selectedScope", { n: selectedSessionIds.length, hidden: selectedSessionIds.length - selectedVisibleCount }) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy || selectedSessionIds.length === 0, onClick: () => setSelectedSessionIds([]), children: t("archives.clearSelection") }), (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") })]
|
|
823
915
|
}), 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
916
|
const target = archivedBatchTargetForGroup(group.key);
|
|
825
917
|
const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
|
|
@@ -834,7 +926,7 @@
|
|
|
834
926
|
className: "dsham_settingsRow",
|
|
835
927
|
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
928
|
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, {}) })]
|
|
929
|
+
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
930
|
})]
|
|
839
931
|
}, session.id))
|
|
840
932
|
})]
|
|
@@ -3355,172 +3447,13 @@
|
|
|
3355
3447
|
return compareText(left.title, right.title) || compareText(left.key, right.key);
|
|
3356
3448
|
});
|
|
3357
3449
|
}
|
|
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
3450
|
const zh = {
|
|
3451
|
+
"archives.viewConversation": "\u67E5\u770B\u5BF9\u8BDD",
|
|
3452
|
+
"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",
|
|
3453
|
+
"archives.sessionNotRetained": "\u5BBF\u4E3B\u672A\u4FDD\u7559\u76EE\u6807\u4F1A\u8BDD\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u6216\u6062\u590D\u540E\u518D\u8BD5\u3002",
|
|
3454
|
+
"archives.restoreOpen": "\u6062\u590D\u5E76\u6253\u5F00",
|
|
3455
|
+
"archives.selectedScope": "\u5DF2\u9009 {n} \u6761\uFF0C\u5176\u4E2D {hidden} \u6761\u4E0D\u5728\u5F53\u524D\u7ED3\u679C",
|
|
3456
|
+
"archives.clearSelection": "\u6E05\u7A7A\u9009\u62E9",
|
|
3524
3457
|
"group.ungrouped": "\u672A\u5206\u7EC4",
|
|
3525
3458
|
"session.new": "\u65B0\u4F1A\u8BDD",
|
|
3526
3459
|
"section.workspaces": "\u5DE5\u4F5C\u533A",
|
|
@@ -3535,7 +3468,6 @@
|
|
|
3535
3468
|
"sessions.expand": "\u5C55\u5F00\u5176\u4F59 {n} \u4E2A\u4F1A\u8BDD",
|
|
3536
3469
|
"sessions.collapse": "\u6536\u8D77",
|
|
3537
3470
|
"empty.none": "\u6682\u65E0\u4F1A\u8BDD",
|
|
3538
|
-
"empty.noMatches": "\u65E0\u5339\u914D\u7ED3\u679C",
|
|
3539
3471
|
"workspace.add": "\u6DFB\u52A0\u5DE5\u4F5C\u533A",
|
|
3540
3472
|
"search.sessions.aria": "\u641C\u7D22\u4F1A\u8BDD",
|
|
3541
3473
|
"search.placeholder": "\u641C\u7D22\u4F1A\u8BDD\u2026",
|
|
@@ -3566,7 +3498,6 @@
|
|
|
3566
3498
|
"archives.sessionCount": "{n} \u4E2A\u804A\u5929",
|
|
3567
3499
|
"archives.selectAllFiltered": "\u5168\u9009\u5F53\u524D\u7B5B\u9009\u7ED3\u679C",
|
|
3568
3500
|
"archives.selectSession": "\u9009\u62E9\u4F1A\u8BDD\u201C{name}\u201D",
|
|
3569
|
-
"archives.selectedCount": "\u5DF2\u9009 {n} \u4E2A",
|
|
3570
3501
|
"archives.restoreSelected": "\u6062\u590D\u6240\u9009",
|
|
3571
3502
|
"archives.deleteSelected": "\u5220\u9664\u6240\u9009",
|
|
3572
3503
|
"archives.timestamp": "{date}\uFF0C{time}",
|
|
@@ -3585,7 +3516,6 @@
|
|
|
3585
3516
|
"archives.deleteSelectedTitle": "\u5220\u9664\u6240\u9009\u5DF2\u5F52\u6863\u804A\u5929",
|
|
3586
3517
|
"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
3518
|
"archives.deleteSelectedConfirm": "\u5220\u9664\u6240\u9009\u804A\u5929",
|
|
3588
|
-
"archives.deleteAllPending": "\u6B63\u5728\u5220\u9664\u5DF2\u5F52\u6863\u804A\u5929\u2026",
|
|
3589
3519
|
"archives.deleteProjectTitle": "\u5220\u9664\u201C{name}\u201D\u4E2D\u7684\u5DF2\u5F52\u6863\u804A\u5929",
|
|
3590
3520
|
"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
3521
|
"archives.deleteProjectConfirm": "\u5220\u9664\u8BE5\u9879\u76EE\u7684\u5168\u90E8\u804A\u5929",
|
|
@@ -3626,8 +3556,6 @@
|
|
|
3626
3556
|
"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
3557
|
"archiveWorkspace.confirm": "\u5168\u90E8\u5F52\u6863",
|
|
3628
3558
|
"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
3559
|
"actions.workspace.aria": "\u5DE5\u4F5C\u533A\u201C{name}\u201D\u7684\u64CD\u4F5C",
|
|
3632
3560
|
"actions.session.aria": "\u4F1A\u8BDD\u201C{name}\u201D\u7684\u64CD\u4F5C",
|
|
3633
3561
|
"actions.newSession.aria": "\u5728\u201C{name}\u201D\u4E2D\u65B0\u5EFA\u4F1A\u8BDD",
|
|
@@ -3651,6 +3579,12 @@
|
|
|
3651
3579
|
"time.ago": "{t}\u524D"
|
|
3652
3580
|
};
|
|
3653
3581
|
const en = {
|
|
3582
|
+
"archives.viewConversation": "View conversation",
|
|
3583
|
+
"archives.navigationUnavailable": "This host cannot keep the conversation archived while opening it. Use \u201CRestore and open\u201D instead.",
|
|
3584
|
+
"archives.sessionNotRetained": "The host did not keep the requested conversation open. Try again, or restore it before opening.",
|
|
3585
|
+
"archives.restoreOpen": "Restore and open",
|
|
3586
|
+
"archives.selectedScope": "{n} selected, {hidden} outside current results",
|
|
3587
|
+
"archives.clearSelection": "Clear selection",
|
|
3654
3588
|
"group.ungrouped": "Ungrouped",
|
|
3655
3589
|
"session.new": "New Session",
|
|
3656
3590
|
"section.workspaces": "Workspaces",
|
|
@@ -3665,7 +3599,6 @@
|
|
|
3665
3599
|
"sessions.expand": "Show {n} more sessions",
|
|
3666
3600
|
"sessions.collapse": "Show less",
|
|
3667
3601
|
"empty.none": "No sessions yet",
|
|
3668
|
-
"empty.noMatches": "No matches",
|
|
3669
3602
|
"workspace.add": "Add workspace",
|
|
3670
3603
|
"search.sessions.aria": "Search sessions",
|
|
3671
3604
|
"search.placeholder": "Search sessions...",
|
|
@@ -3696,7 +3629,6 @@
|
|
|
3696
3629
|
"archives.sessionCount": "{n} chats",
|
|
3697
3630
|
"archives.selectAllFiltered": "Select all results",
|
|
3698
3631
|
"archives.selectSession": "Select chat {name}",
|
|
3699
|
-
"archives.selectedCount": "{n} selected",
|
|
3700
3632
|
"archives.restoreSelected": "Restore selected",
|
|
3701
3633
|
"archives.deleteSelected": "Delete selected",
|
|
3702
3634
|
"archives.timestamp": "{date}, {time}",
|
|
@@ -3715,7 +3647,6 @@
|
|
|
3715
3647
|
"archives.deleteSelectedTitle": "Delete selected archived chats",
|
|
3716
3648
|
"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
3649
|
"archives.deleteSelectedConfirm": "Delete selected chats",
|
|
3718
|
-
"archives.deleteAllPending": "Deleting archived chats\u2026",
|
|
3719
3650
|
"archives.deleteProjectTitle": "Delete archived chats in {name}",
|
|
3720
3651
|
"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
3652
|
"archives.deleteProjectConfirm": "Delete all project chats",
|
|
@@ -3756,8 +3687,6 @@
|
|
|
3756
3687
|
"archiveWorkspace.desc": "This archives the chats in \u201C{name}\u201D. You can find them later in Archived chats.",
|
|
3757
3688
|
"archiveWorkspace.confirm": "Archive all",
|
|
3758
3689
|
"archiveWorkspace.pending": "Archiving chats\u2026",
|
|
3759
|
-
"sessions.count.one": "{n} session",
|
|
3760
|
-
"sessions.count.other": "{n} sessions",
|
|
3761
3690
|
"actions.workspace.aria": "Workspace actions for {name}",
|
|
3762
3691
|
"actions.session.aria": "Session actions for {name}",
|
|
3763
3692
|
"actions.newSession.aria": "New session in {name}",
|
|
@@ -3828,6 +3757,15 @@
|
|
|
3828
3757
|
createIcon: createPluginUpdateIcon
|
|
3829
3758
|
}), "dsh-archive-manager: plugin update ui");
|
|
3830
3759
|
const uiWorkspaceAt = () => ctx.get("uiWorkspace");
|
|
3760
|
+
const archiveViewState = {};
|
|
3761
|
+
let archiveNavigation;
|
|
3762
|
+
ctx.effect(() => {
|
|
3763
|
+
archiveNavigation = allowArchivedNavigation(uiWorkspaceAt(), ctx.sessions, ctx.workspaces, {
|
|
3764
|
+
onOpened: () => ctx.get("layout")?.selectPanel(null)
|
|
3765
|
+
});
|
|
3766
|
+
return () => archiveNavigation.dispose();
|
|
3767
|
+
}, "archive-manager: explicit archived navigation");
|
|
3768
|
+
const openConversation = (sessionId) => archiveNavigation.open(sessionId);
|
|
3831
3769
|
const searchSessions = async (query, signal) => {
|
|
3832
3770
|
const result = await ctx.sessions.search(query, signal);
|
|
3833
3771
|
if (!result.ok) throw new Error(result.error.message);
|
|
@@ -3970,11 +3908,15 @@
|
|
|
3970
3908
|
unarchiveSessions,
|
|
3971
3909
|
deleteArchivedSessions,
|
|
3972
3910
|
archivedSessionMetadata,
|
|
3911
|
+
openConversation,
|
|
3912
|
+
viewState: archiveViewState,
|
|
3973
3913
|
t: ctx.locale.bind(NS)
|
|
3974
3914
|
})
|
|
3975
|
-
},
|
|
3915
|
+
}, ArchivedSessionsSection));
|
|
3976
3916
|
}
|
|
3977
3917
|
exports.__test = {
|
|
3918
|
+
ArchivedSessionsSection,
|
|
3919
|
+
zh,
|
|
3978
3920
|
displayTitle,
|
|
3979
3921
|
sessionVisible,
|
|
3980
3922
|
indexSubagentDescendants,
|