@michengai/dsh-archive-manager 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -7
- package/CHANGELOG.zh-CN.md +8 -7
- package/README.md +10 -7
- package/README.zh-CN.md +10 -7
- package/lib/client.js +380 -127
- package/lib/workspace.js +191 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
The five most recent published versions are listed below.
|
|
6
6
|
|
|
7
|
+
## 0.1.14 — 2026-08-24
|
|
8
|
+
|
|
9
|
+
- Added project-scoped batch restore and permanent deletion, plus a page-level **Restore all** action.
|
|
10
|
+
- Added archived-chat sorting by last update, creation time, or title, backed by authoritative host creation metadata.
|
|
11
|
+
- Added confirmation, success, and partial-failure feedback for batch archive operations.
|
|
12
|
+
|
|
13
|
+
Published package: [`@michengai/dsh-archive-manager@0.1.14`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.14).
|
|
14
|
+
|
|
7
15
|
## 0.1.13 — 2026-08-23
|
|
8
16
|
|
|
9
17
|
- Added bilingual changelogs covering the five most recent releases.
|
|
@@ -32,10 +40,3 @@ Release tag: [`v0.1.11`](https://github.com/MichengAI/dsh-archive-manager/tree/v
|
|
|
32
40
|
- Counted only visible conversations in archive totals.
|
|
33
41
|
|
|
34
42
|
Release tag: [`v0.1.10`](https://github.com/MichengAI/dsh-archive-manager/tree/v0.1.10).
|
|
35
|
-
|
|
36
|
-
## 0.1.9 — 2026-08-17
|
|
37
|
-
|
|
38
|
-
- Removed client-side cascade deletes and surfaced sidebar operation errors.
|
|
39
|
-
- Restyled the project filter and refreshed plugin documentation.
|
|
40
|
-
|
|
41
|
-
Release tag: [`v0.1.9`](https://github.com/MichengAI/dsh-archive-manager/tree/v0.1.9).
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
以下记录最近发布的五个版本。
|
|
6
6
|
|
|
7
|
+
## 0.1.14 — 2026-08-24
|
|
8
|
+
|
|
9
|
+
- 新增项目级批量恢复和永久删除,并在页面顶部增加「全部恢复」。
|
|
10
|
+
- 新增按更新时间、创建时间或标题排序归档聊天;创建时间来自宿主权威元数据。
|
|
11
|
+
- 为批量归档操作新增确认、成功和部分失败反馈。
|
|
12
|
+
|
|
13
|
+
发布包:[`@michengai/dsh-archive-manager@0.1.14`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.14)。
|
|
14
|
+
|
|
7
15
|
## 0.1.13 — 2026-08-23
|
|
8
16
|
|
|
9
17
|
- 新增中英文更新日志,展示最近五个发布版本。
|
|
@@ -32,10 +40,3 @@
|
|
|
32
40
|
- 归档计数仅统计当前可见会话。
|
|
33
41
|
|
|
34
42
|
发布标签:[`v0.1.10`](https://github.com/MichengAI/dsh-archive-manager/tree/v0.1.10)。
|
|
35
|
-
|
|
36
|
-
## 0.1.9 — 2026-08-17
|
|
37
|
-
|
|
38
|
-
- 移除客户端级联删除,并显示侧栏操作错误。
|
|
39
|
-
- 重做项目筛选样式并刷新插件文档。
|
|
40
|
-
|
|
41
|
-
发布标签:[`v0.1.9`](https://github.com/MichengAI/dsh-archive-manager/tree/v0.1.9)。
|
package/README.md
CHANGED
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
24
|
- Archive a session from the sidebar session menu.
|
|
25
|
-
- Search archived chats and filter them by workspace in **Settings → Archived**.
|
|
26
|
-
- Restore a session to its original workspace with **Unarchive**.
|
|
27
|
-
-
|
|
25
|
+
- Search archived chats, sort them by update time, creation time, or title, and filter them by workspace in **Settings → Archived**.
|
|
26
|
+
- Restore a session to its original workspace with **Unarchive**.
|
|
27
|
+
- Restore or permanently delete every archived chat in a project group.
|
|
28
|
+
- Restore all archived chats from the page header.
|
|
29
|
+
- Permanently delete a confirmed session, its workspace association, archive marker, and projection cache.
|
|
28
30
|
- Delete all archived chats after confirmation, including child agents.
|
|
29
31
|
- Remove unloaded deleted sessions from connected sidebars immediately.
|
|
30
32
|
- Paste one sentence into DSH, Codex, or WorkBuddy and let that agent install the plugin locally.
|
|
@@ -35,7 +37,7 @@ Open the sidebar session menu and choose **Archive session**:
|
|
|
35
37
|
|
|
36
38
|

|
|
37
39
|
|
|
38
|
-
Search, filter by project, unarchive, or permanently delete chats in **Settings → Archived**:
|
|
40
|
+
Search, sort, filter by project, unarchive, or permanently delete chats in **Settings → Archived**:
|
|
39
41
|
|
|
40
42
|

|
|
41
43
|
|
|
@@ -129,9 +131,10 @@ Restart DSH Web and hard-refresh the browser. `dsh plugin ... add .` reads the p
|
|
|
129
131
|
|
|
130
132
|
1. Open the sidebar session menu and choose **Archive session**.
|
|
131
133
|
2. Open **Settings → Archived** to inspect sessions by workspace.
|
|
132
|
-
3.
|
|
133
|
-
4. Select **Unarchive** to restore
|
|
134
|
-
5.
|
|
134
|
+
3. Search by title, sort by update time, creation time, or title, or filter the list by project.
|
|
135
|
+
4. Select **Unarchive** to restore one session, or select **Restore all** in the page header.
|
|
136
|
+
5. Open a project heading's action menu to restore or delete all of that project's archived chats.
|
|
137
|
+
6. Use the delete icon to remove one session permanently, then confirm the deletion. **It cannot be undone.**
|
|
135
138
|
|
|
136
139
|
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.
|
|
137
140
|
|
package/README.zh-CN.md
CHANGED
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
## 功能概览
|
|
23
23
|
|
|
24
24
|
- 在侧栏会话菜单中选择「归档会话」。
|
|
25
|
-
- 在「设置 →
|
|
26
|
-
- 安全取消归档,将会话恢复到原工作区位置。
|
|
27
|
-
-
|
|
25
|
+
- 在「设置 → 已归档」按工作区查看归档会话,并支持搜索、按更新时间/创建时间/标题排序和按项目筛选。
|
|
26
|
+
- 安全取消归档,将会话恢复到原工作区位置。
|
|
27
|
+
- 在项目分组中批量恢复或永久删除该项目的全部已归档聊天。
|
|
28
|
+
- 在页面顶部一键恢复全部已归档聊天。
|
|
29
|
+
- 经确认后永久删除会话、工作区归属、归档标记和投影缓存。
|
|
28
30
|
- 经确认后删除全部已归档聊天,包含子代理。
|
|
29
31
|
- 已删除的未加载归档会话会立即从已连接客户端的侧栏移除。
|
|
30
32
|
- 可把一句话复制到 DSH、Codex 或 WorkBuddy,让对方代装到本机 DSH。
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
|
|
36
38
|

|
|
37
39
|
|
|
38
|
-
在「设置 →
|
|
40
|
+
在「设置 → 已归档」中搜索、排序、按项目筛选、取消归档或永久删除:
|
|
39
41
|
|
|
40
42
|

|
|
41
43
|
|
|
@@ -129,9 +131,10 @@ dsh --profile web --dump-config
|
|
|
129
131
|
|
|
130
132
|
1. 在侧栏右键或打开会话菜单,选择「归档会话」。
|
|
131
133
|
2. 打开「设置 → 已归档」,按工作区查看归档会话。
|
|
132
|
-
3.
|
|
133
|
-
4.
|
|
134
|
-
5.
|
|
134
|
+
3. 按标题搜索,按更新时间、创建时间或标题排序,或按项目筛选列表。
|
|
135
|
+
4. 点击「取消归档」恢复单个会话,或在顶部点击「全部恢复」。
|
|
136
|
+
5. 打开项目标题右侧菜单,可恢复或删除该项目的全部已归档聊天。
|
|
137
|
+
6. 点击删除图标永久移除单个会话;删除前确认提示。**删除无法撤销。**
|
|
135
138
|
|
|
136
139
|
安装或升级后找不到入口时,重启 DSH Web 并硬刷新浏览器;入口位于「设置」中,连接器之后。
|
|
137
140
|
|
package/lib/client.js
CHANGED
|
@@ -29,12 +29,45 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
return value;
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
const deletedSchema = {
|
|
33
|
-
parse(value) {
|
|
34
|
-
if (typeof value !== "object" || value === null || Array.isArray(value) || value.deleted !== true) throw new TypeError("deleted must be true");
|
|
35
|
-
return value;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
32
|
+
const deletedSchema = {
|
|
33
|
+
parse(value) {
|
|
34
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value.deleted !== true) throw new TypeError("deleted must be true");
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const archivedBatchTargetSchema = {
|
|
39
|
+
parse(value) {
|
|
40
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("target must be an object");
|
|
41
|
+
if (value.scope === "all" || value.scope === "ungrouped") return value;
|
|
42
|
+
if (value.scope === "workspace" && typeof value.workspaceId === "string" && value.workspaceId.length > 0) return value;
|
|
43
|
+
throw new TypeError("target.scope must be all, ungrouped, or workspace with a non-empty workspaceId");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const unarchivedBatchSchema = {
|
|
47
|
+
parse(value) {
|
|
48
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
49
|
+
if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIds must be a string array");
|
|
50
|
+
if (!Array.isArray(value.unarchivedSessionIds) || value.unarchivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("unarchivedSessionIds must be a string array");
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const deletedBatchSchema = {
|
|
55
|
+
parse(value) {
|
|
56
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
57
|
+
for (const key of ["requestedSessionIds", "deletedSessionIds", "skippedSessionIds"]) {
|
|
58
|
+
if (!Array.isArray(value[key]) || value[key].some((id) => typeof id !== "string")) throw new TypeError(`${key} must be a string array`);
|
|
59
|
+
}
|
|
60
|
+
if (!Array.isArray(value.failures) || value.failures.some((failure) => typeof failure !== "object" || failure === null || typeof failure.sessionId !== "string" || typeof failure.message !== "string")) throw new TypeError("failures must contain sessionId/message objects");
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const archivedSessionMetadataSchema = {
|
|
65
|
+
parse(value) {
|
|
66
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !Array.isArray(value.items)) throw new TypeError("result.items must be an array");
|
|
67
|
+
if (value.items.some((item) => typeof item !== "object" || item === null || typeof item.sessionId !== "string" || typeof item.createdAt !== "number" || !Number.isFinite(item.createdAt))) throw new TypeError("items must contain sessionId/createdAt objects");
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
38
71
|
/**
|
|
39
72
|
* 客户端通过 `ctx.remote.$mount` 注册 workspaceRegistry 的远程方法。
|
|
40
73
|
* 调用走 typert gateway SRC 路径,不影响既有 `/api/workspace.*` 网关。
|
|
@@ -61,8 +94,8 @@ window.__ModuleLoader__.load({
|
|
|
61
94
|
},
|
|
62
95
|
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
63
96
|
},
|
|
64
|
-
{
|
|
65
|
-
id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteSession",
|
|
97
|
+
{
|
|
98
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteSession",
|
|
66
99
|
service: "workspaceRegistry",
|
|
67
100
|
namespace: "workspaceRegistry",
|
|
68
101
|
method: "deleteSession",
|
|
@@ -78,8 +111,60 @@ window.__ModuleLoader__.load({
|
|
|
78
111
|
typeSymbol: "@michengai/dsh-archive-manager/types#Deleted",
|
|
79
112
|
schema: deletedSchema
|
|
80
113
|
},
|
|
81
|
-
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
82
|
-
}
|
|
114
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/unarchiveSessions",
|
|
118
|
+
service: "workspaceRegistry",
|
|
119
|
+
namespace: "workspaceRegistry",
|
|
120
|
+
method: "unarchiveSessions",
|
|
121
|
+
invocation: { kind: "direct" },
|
|
122
|
+
parameters: [{
|
|
123
|
+
name: "target",
|
|
124
|
+
wire: "target",
|
|
125
|
+
source: "json",
|
|
126
|
+
codec: { mode: "strict", typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget", schema: archivedBatchTargetSchema }
|
|
127
|
+
}],
|
|
128
|
+
result: {
|
|
129
|
+
mode: "strict",
|
|
130
|
+
typeSymbol: "@michengai/dsh-archive-manager/types#UnarchivedBatch",
|
|
131
|
+
schema: unarchivedBatchSchema
|
|
132
|
+
},
|
|
133
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteArchivedSessions",
|
|
137
|
+
service: "workspaceRegistry",
|
|
138
|
+
namespace: "workspaceRegistry",
|
|
139
|
+
method: "deleteArchivedSessions",
|
|
140
|
+
invocation: { kind: "direct" },
|
|
141
|
+
parameters: [{
|
|
142
|
+
name: "target",
|
|
143
|
+
wire: "target",
|
|
144
|
+
source: "json",
|
|
145
|
+
codec: { mode: "strict", typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget", schema: archivedBatchTargetSchema }
|
|
146
|
+
}],
|
|
147
|
+
result: {
|
|
148
|
+
mode: "strict",
|
|
149
|
+
typeSymbol: "@michengai/dsh-archive-manager/types#DeletedBatch",
|
|
150
|
+
schema: deletedBatchSchema
|
|
151
|
+
},
|
|
152
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/archivedSessionMetadata",
|
|
156
|
+
service: "workspaceRegistry",
|
|
157
|
+
namespace: "workspaceRegistry",
|
|
158
|
+
method: "archivedSessionMetadata",
|
|
159
|
+
invocation: { kind: "direct" },
|
|
160
|
+
parameters: [],
|
|
161
|
+
result: {
|
|
162
|
+
mode: "strict",
|
|
163
|
+
typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedSessionMetadata",
|
|
164
|
+
schema: archivedSessionMetadataSchema
|
|
165
|
+
},
|
|
166
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
167
|
+
}
|
|
83
168
|
]
|
|
84
169
|
};
|
|
85
170
|
/** Archived-session row presentation classes (theme-token driven). */
|
|
@@ -2495,7 +2580,8 @@ window.__ModuleLoader__.load({
|
|
|
2495
2580
|
}
|
|
2496
2581
|
//#endregion
|
|
2497
2582
|
//#region dsh-archive-manager: settings section
|
|
2498
|
-
const ARCHIVE_SETTINGS_CSS = ".dsham_settings{box-sizing:border-box;width:min(100%,760px);margin:0 auto;padding:0 0 32px;color:var(--dsw-alias-label-primary)}.dsham_settingsHeader{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:12px}.dsham_settings h2{margin:0;font-size:20px;font-weight:650;letter-spacing:-.2px;line-height:28px}.dsham_settingsIntro{margin:4px 0 0;max-width:42em;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.dsham_settingsDanger{display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:0 12px;color:var(--dsw-alias-state-error-primary);background:transparent;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsDanger:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)}.dsham_settingsToolbar{display:flex;gap:8px;margin-bottom:16px}.dsham_settingsSearch{display:flex;align-items:center;gap:8px;min-width:0;flex:1;height:32px;padding:0 12px;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-button-elevated-fill));border:1px solid var(--dsw-alias-border-l2);border-radius:8px}.dsham_settingsSearch:focus-within{border-color:var(--dsw-alias-label-tertiary)}.dsham_settingsSearch input{width:100%;min-width:0;padding:0;color:var(--dsw-alias-label-primary);background:transparent;border:0;outline:0;font:inherit;font-size:12px}.dsham_settingsSearch input::placeholder{color:var(--dsw-alias-label-tertiary)}.dsham_settingsFilter{position:relative;min-width:168px;flex:none}.dsham_selectTrigger{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:32px;padding:0 10px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-button-elevated-fill));border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;line-height:20px;text-align:left}.dsham_selectTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_selectTrigger:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_selectTrigger[aria-expanded='true']{border-color:var(--dsw-alias-state-success-primary)}.dsham_selectValue{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsham_selectCaret{flex:none;width:12px;height:12px;color:var(--dsw-alias-label-tertiary)}.dsham_selectMenu{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:30;box-sizing:border-box;min-width:100%;max-height:280px;overflow:auto;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2));box-shadow:var(--dsw-shadow-lv3)}.dsham_selectOption{box-sizing:border-box;display:flex;align-items:center;width:100%;min-height:32px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}.dsham_selectOption:hover,.dsham_selectOption[data-active='true']{background:var(--dsw-alias-interactive-bg-hover)}.dsham_selectOption[aria-selected='true']{color:var(--dsw-alias-state-success-primary)}.dsham_settingsGroup{margin:0 0 20px}.dsham_settingsGroupHeading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 14px}.dsham_settingsGroupTitle{display:flex;align-items:center;gap:8px;min-width:0;margin:0;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.dsham_settingsGroupTitle svg{flex:none;color:var(--dsw-alias-label-secondary)}.dsham_settingsCount{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsList{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-button-elevated-fill))}.dsham_settingsRow{display:flex;align-items:center;gap:12px;min-height:60px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_settingsRow:last-child{border-bottom:0}.dsham_settingsContent{min-width:0;flex:1}.dsham_settingsTitle{overflow:hidden;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.dsham_settingsMeta{margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}.dsham_settingsActions{display:flex;align-items:center;gap:8px}.dsham_settingsAction{min-height:32px;padding:0 12px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsAction:hover{filter:brightness(1.12)}.dsham_settingsDelete{display:flex;align-items:center;justify-content:center;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsDelete:hover{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsEmpty{padding:28px 8px;color:var(--dsw-alias-label-secondary);text-align:center}.dsham_settingsError{margin-top:10px;color:var(--dsw-alias-state-error-primary);font-size:12px}@media(max-width:720px){.dsham_settings{width:100%;margin:28px auto 48px;padding:0 16px}.dsham_settingsHeader{margin-bottom:28px}.dsham_settingsToolbar{flex-wrap:wrap;margin-bottom:28px}.dsham_settingsSearch{flex-basis:100%}.dsham_settingsFilter{flex:1;min-width:0}.dsham_settingsGroup{margin-bottom:32px}.dsham_settingsRow{padding:10px 12px}.dsham_settingsActions{gap:4px}}";
|
|
2583
|
+
const ARCHIVE_SETTINGS_CSS = ".dsham_settings{box-sizing:border-box;width:min(100%,760px);margin:0 auto;padding:0 0 32px;color:var(--dsw-alias-label-primary)}.dsham_settingsHeader{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:12px}.dsham_settings h2{margin:0;font-size:20px;font-weight:650;letter-spacing:-.2px;line-height:28px}.dsham_settingsIntro{margin:4px 0 0;max-width:42em;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.dsham_settingsDanger{display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:0 12px;color:var(--dsw-alias-state-error-primary);background:transparent;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsDanger:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)}.dsham_settingsToolbar{display:flex;gap:8px;margin-bottom:16px}.dsham_settingsSearch{display:flex;align-items:center;gap:8px;min-width:0;flex:1;height:32px;padding:0 12px;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-button-elevated-fill));border:1px solid var(--dsw-alias-border-l2);border-radius:8px}.dsham_settingsSearch:focus-within{border-color:var(--dsw-alias-label-tertiary)}.dsham_settingsSearch input{width:100%;min-width:0;padding:0;color:var(--dsw-alias-label-primary);background:transparent;border:0;outline:0;font:inherit;font-size:12px}.dsham_settingsSearch input::placeholder{color:var(--dsw-alias-label-tertiary)}.dsham_settingsFilter{position:relative;min-width:168px;flex:none}.dsham_selectTrigger{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:32px;padding:0 10px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-button-elevated-fill));border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;line-height:20px;text-align:left}.dsham_selectTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_selectTrigger:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_selectTrigger[aria-expanded='true']{border-color:var(--dsw-alias-state-success-primary)}.dsham_selectValue{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsham_selectCaret{flex:none;width:12px;height:12px;color:var(--dsw-alias-label-tertiary)}.dsham_selectMenu{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:30;box-sizing:border-box;min-width:100%;max-height:280px;overflow:auto;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2));box-shadow:var(--dsw-shadow-lv3)}.dsham_selectOption{box-sizing:border-box;display:flex;align-items:center;width:100%;min-height:32px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}.dsham_selectOption:hover,.dsham_selectOption[data-active='true']{background:var(--dsw-alias-interactive-bg-hover)}.dsham_selectOption[aria-selected='true']{color:var(--dsw-alias-state-success-primary)}.dsham_settingsGroup{margin:0 0 20px}.dsham_settingsGroupHeading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 14px}.dsham_settingsGroupTitle{display:flex;align-items:center;gap:8px;min-width:0;margin:0;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.dsham_settingsGroupTitle svg{flex:none;color:var(--dsw-alias-label-secondary)}.dsham_settingsCount{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsList{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-button-elevated-fill))}.dsham_settingsRow{display:flex;align-items:center;gap:12px;min-height:60px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_settingsRow:last-child{border-bottom:0}.dsham_settingsContent{min-width:0;flex:1}.dsham_settingsTitle{overflow:hidden;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.dsham_settingsMeta{margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}.dsham_settingsActions{display:flex;align-items:center;gap:8px}.dsham_settingsAction{min-height:32px;padding:0 12px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsAction:hover{filter:brightness(1.12)}.dsham_settingsDelete{display:flex;align-items:center;justify-content:center;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsDelete:hover{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsEmpty{padding:28px 8px;color:var(--dsw-alias-label-secondary);text-align:center}.dsham_settingsError{margin-top:10px;color:var(--dsw-alias-state-error-primary);font-size:12px}@media(max-width:720px){.dsham_settings{width:100%;margin:28px auto 48px;padding:0 16px}.dsham_settingsHeader{margin-bottom:28px}.dsham_settingsToolbar{flex-wrap:wrap;margin-bottom:28px}.dsham_settingsSearch{flex-basis:100%}.dsham_settingsFilter{flex:1;min-width:0}.dsham_settingsGroup{margin-bottom:32px}.dsham_settingsRow{padding:10px 12px}.dsham_settingsActions{gap:4px}}";
|
|
2584
|
+
const ARCHIVE_SETTINGS_BATCH_CSS = ".dsham_settingsHeaderActions,.dsham_settingsGroupMeta{display:flex;align-items:center;gap:8px;flex:none}.dsham_settingsRestoreAll{display:inline-flex;align-items:center;min-height:32px;padding:0 12px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsRestoreAll:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsRestoreAll:disabled,.dsham_settingsDanger:disabled,.dsham_settingsGroupMenu:disabled{cursor:not-allowed;opacity:.5}.dsham_settingsGroupMenu{display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsGroupMenu:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsStatus{margin-top:10px;color:var(--dsw-alias-label-secondary);font-size:12px}@media(max-width:720px){.dsham_settingsHeader{flex-direction:column}.dsham_settingsHeaderActions{align-self:flex-end}}";
|
|
2499
2585
|
const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:0 auto!important}@media(max-width:720px){.dsham_settings{margin:0 auto!important}}";
|
|
2500
2586
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
|
|
2501
2587
|
const tag = document.createElement("style");
|
|
@@ -2504,8 +2590,8 @@ window.__ModuleLoader__.load({
|
|
|
2504
2590
|
tag.textContent = ARCHIVE_SETTINGS_LAYOUT_OVERRIDE;
|
|
2505
2591
|
document.head.appendChild(tag);
|
|
2506
2592
|
}
|
|
2507
|
-
/**
|
|
2508
|
-
function ArchiveProjectSelect({ id, value, options, onChange, "aria-label": ariaLabel }) {
|
|
2593
|
+
/** 设置页筛选/排序:自定义菜单,避免原生 select 弹出系统浅色下拉。 */
|
|
2594
|
+
function ArchiveProjectSelect({ id, value, options, onChange, "aria-label": ariaLabel }) {
|
|
2509
2595
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2510
2596
|
const selectedIndex = Math.max(0, options.findIndex((option) => option.value === value));
|
|
2511
2597
|
const [active, setActive] = (0, react.useState)(selectedIndex);
|
|
@@ -2634,10 +2720,57 @@ window.__ModuleLoader__.load({
|
|
|
2634
2720
|
children: option.label
|
|
2635
2721
|
}, option.value === "all" ? "all" : option.value))
|
|
2636
2722
|
}) : null]
|
|
2637
|
-
});
|
|
2638
|
-
}
|
|
2639
|
-
/**
|
|
2640
|
-
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
/** 项目标题右侧的批量恢复/删除菜单,复用宿主菜单组件的键盘和焦点行为。 */
|
|
2726
|
+
function ArchivedGroupActions({ group, busy, onRestore, onDelete, t }) {
|
|
2727
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2728
|
+
const ungrouped = group.key === ARCHIVE_UNGROUPED_KEY;
|
|
2729
|
+
const items = [{
|
|
2730
|
+
id: "restore",
|
|
2731
|
+
label: t(ungrouped ? "archives.restoreUngrouped" : "archives.restoreProject")
|
|
2732
|
+
}, {
|
|
2733
|
+
id: "delete",
|
|
2734
|
+
label: t(ungrouped ? "archives.deleteUngrouped" : "archives.deleteProject"),
|
|
2735
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
|
|
2736
|
+
danger: true
|
|
2737
|
+
}];
|
|
2738
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
2739
|
+
open,
|
|
2740
|
+
onClose: () => setOpen(false),
|
|
2741
|
+
items,
|
|
2742
|
+
onSelect: (id) => {
|
|
2743
|
+
setOpen(false);
|
|
2744
|
+
if (id === "restore") onRestore();
|
|
2745
|
+
else if (id === "delete") onDelete();
|
|
2746
|
+
},
|
|
2747
|
+
portal: true,
|
|
2748
|
+
anchor: (0, react_jsx_runtime.jsx)("button", {
|
|
2749
|
+
type: "button",
|
|
2750
|
+
className: "dsham_settingsGroupMenu",
|
|
2751
|
+
disabled: busy,
|
|
2752
|
+
"aria-label": t(ungrouped ? "archives.ungroupedActions" : "archives.projectActions", { name: group.title }),
|
|
2753
|
+
onClick: () => setOpen((current) => !current),
|
|
2754
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
|
|
2755
|
+
})
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
/** 设置页“未分组”桶的稳定 key(workspaceId 均为非空 uuid,不会冲突)。 */
|
|
2759
|
+
const ARCHIVE_UNGROUPED_KEY = "__ungrouped__";
|
|
2760
|
+
function archivedBatchTargetForGroup(groupKey) {
|
|
2761
|
+
return groupKey === ARCHIVE_UNGROUPED_KEY ? { scope: "ungrouped" } : { scope: "workspace", workspaceId: groupKey };
|
|
2762
|
+
}
|
|
2763
|
+
/** 客户端只用此派生显示权威批次计数;宿主会用自己的持久状态重新解析。 */
|
|
2764
|
+
function deriveArchivedBatchIds(archivedSessionIds, items, target) {
|
|
2765
|
+
const ids = [...new Set((archivedSessionIds ?? []).filter((id) => typeof id === "string" && id.length > 0))];
|
|
2766
|
+
if (target.scope === "all") return ids;
|
|
2767
|
+
if (target.scope === "workspace") {
|
|
2768
|
+
const accounted = new Set(items.find((workspace) => workspace.workspaceId === target.workspaceId)?.sessionIds ?? []);
|
|
2769
|
+
return ids.filter((id) => accounted.has(id));
|
|
2770
|
+
}
|
|
2771
|
+
const accounted = new Set(items.flatMap((workspace) => workspace.sessionIds));
|
|
2772
|
+
return ids.filter((id) => !accounted.has(id));
|
|
2773
|
+
}
|
|
2641
2774
|
/**
|
|
2642
2775
|
* 设置页归档分组派生:按工作区分组,组 key 用 workspaceId(上游允许
|
|
2643
2776
|
* 不同路径的工作区同名,title 不能作 React key / 筛选 value),未归入
|
|
@@ -2648,7 +2781,7 @@ window.__ModuleLoader__.load({
|
|
|
2648
2781
|
* @param ungroupedLabel - “未分组”显示文案。
|
|
2649
2782
|
* @returns 分组数组(仅含有会话的组),每项含 key / title / sessions。
|
|
2650
2783
|
*/
|
|
2651
|
-
function deriveArchivedGroups(byId, items, archivedSessionIds, ungroupedLabel) {
|
|
2784
|
+
function deriveArchivedGroups(byId, items, archivedSessionIds, ungroupedLabel) {
|
|
2652
2785
|
const byWorkspace = items.map((workspace) => ({
|
|
2653
2786
|
key: workspace.workspaceId,
|
|
2654
2787
|
title: workspace.title,
|
|
@@ -2664,37 +2797,60 @@ window.__ModuleLoader__.load({
|
|
|
2664
2797
|
}
|
|
2665
2798
|
const result = byWorkspace.filter((group) => group.sessions.length > 0);
|
|
2666
2799
|
if (ungrouped.length > 0) result.push({ key: ARCHIVE_UNGROUPED_KEY, title: ungroupedLabel, sessions: ungrouped });
|
|
2667
|
-
return result;
|
|
2668
|
-
}
|
|
2669
|
-
/**
|
|
2670
|
-
*
|
|
2671
|
-
*
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
const
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
const
|
|
2800
|
+
return result;
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2803
|
+
* 归档设置页排序:时间排序同时按每组首条会话排列项目,字母排序则
|
|
2804
|
+
* 同时排列项目名和组内标题;所有输入均复制后排序,不改写 store 快照。
|
|
2805
|
+
*/
|
|
2806
|
+
function sortArchivedGroups(groups, sortBy, createdAtById, t) {
|
|
2807
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), void 0, { numeric: true, sensitivity: "base" });
|
|
2808
|
+
const timestampOf = (session) => {
|
|
2809
|
+
const value = sortBy === "created" ? createdAtById[session.id] : session.updatedAt;
|
|
2810
|
+
return typeof value === "number" && Number.isFinite(value) ? value : Number.NEGATIVE_INFINITY;
|
|
2811
|
+
};
|
|
2812
|
+
const compareSessions = (left, right) => {
|
|
2813
|
+
if (sortBy !== "alphabetical") {
|
|
2814
|
+
const byTime = timestampOf(right) - timestampOf(left);
|
|
2815
|
+
if (Number.isFinite(byTime) && byTime !== 0) return byTime;
|
|
2816
|
+
}
|
|
2817
|
+
return compareText(displayTitle(left, t), displayTitle(right, t)) || compareText(left.id, right.id);
|
|
2818
|
+
};
|
|
2819
|
+
const result = groups.map((group) => ({ ...group, sessions: [...group.sessions].sort(compareSessions) }));
|
|
2820
|
+
return result.sort((left, right) => {
|
|
2821
|
+
if (sortBy !== "alphabetical") {
|
|
2822
|
+
const byTime = timestampOf(right.sessions[0]) - timestampOf(left.sessions[0]);
|
|
2823
|
+
if (Number.isFinite(byTime) && byTime !== 0) return byTime;
|
|
2824
|
+
}
|
|
2825
|
+
return compareText(left.title, right.title) || compareText(left.key, right.key);
|
|
2826
|
+
});
|
|
2827
|
+
}
|
|
2828
|
+
/** 管理设置页中的归档会话,数据直接订阅 DSH 的会话与工作区投影。 */
|
|
2829
|
+
function ArchivedSessionsSection({ sessionStore, workspaceStore, unarchiveSession, deleteSession, unarchiveSessions, deleteArchivedSessions, archivedSessionMetadata, t }) {
|
|
2830
|
+
const sessions = (0, react.useSyncExternalStore)(sessionStore.subscribe, sessionStore.getSnapshot);
|
|
2831
|
+
const workspaceState = (0, react.useSyncExternalStore)(workspaceStore.subscribe, workspaceStore.getSnapshot);
|
|
2832
|
+
const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
|
|
2833
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
2834
|
+
const [error, setError] = (0, react.useState)(null);
|
|
2835
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
2836
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
2837
|
+
const [project, setProject] = (0, react.useState)("all");
|
|
2838
|
+
const [sortBy, setSortBy] = (0, react.useState)("updated");
|
|
2839
|
+
const [createdAtById, setCreatedAtById] = (0, react.useState)({});
|
|
2840
|
+
const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
|
|
2841
|
+
const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
|
|
2842
|
+
(0, react.useEffect)(() => {
|
|
2843
|
+
let cancelled = false;
|
|
2844
|
+
archivedSessionMetadata().then((result) => {
|
|
2845
|
+
if (cancelled) return;
|
|
2846
|
+
setCreatedAtById(Object.fromEntries(result.items.map((item) => [item.sessionId, item.createdAt])));
|
|
2847
|
+
}).catch(() => {
|
|
2848
|
+
if (!cancelled) setCreatedAtById({});
|
|
2849
|
+
});
|
|
2850
|
+
return () => {
|
|
2851
|
+
cancelled = true;
|
|
2852
|
+
};
|
|
2853
|
+
}, [archivedSessionMetadata, workspaceState.archivedSessionIds]);
|
|
2698
2854
|
(0, react.useEffect)(() => {
|
|
2699
2855
|
// 选中的分组消失(如最后一个归档会话被取消归档)时回退到
|
|
2700
2856
|
// “所有项目”,避免筛选值停留在失效 key 上把列表过滤为空。
|
|
@@ -2704,20 +2860,36 @@ window.__ModuleLoader__.load({
|
|
|
2704
2860
|
}, [groups, project]);
|
|
2705
2861
|
const filteredGroups = (0, react.useMemo)(() => {
|
|
2706
2862
|
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
2707
|
-
return
|
|
2863
|
+
return sortedGroups.filter((group) => project === "all" || project === group.key).map((group) => ({
|
|
2708
2864
|
...group,
|
|
2709
2865
|
sessions: group.sessions.filter((session) => normalizedQuery === "" || displayTitle(session, t).toLocaleLowerCase().includes(normalizedQuery))
|
|
2710
2866
|
})).filter((group) => group.sessions.length > 0);
|
|
2711
|
-
}, [
|
|
2712
|
-
const
|
|
2713
|
-
const
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2867
|
+
}, [sortedGroups, project, query, t]);
|
|
2868
|
+
const allBatchTarget = { scope: "all" };
|
|
2869
|
+
const allBatchSessionIds = (0, react.useMemo)(() => deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, allBatchTarget), [workspaceState.archivedSessionIds, workspaceState.items]);
|
|
2870
|
+
const onUnarchive = (sessionId) => {
|
|
2871
|
+
setError(null);
|
|
2872
|
+
setNotice(null);
|
|
2873
|
+
unarchiveSession(sessionId).catch((reason) => {
|
|
2874
|
+
setError(formatUnarchiveError(reason, t));
|
|
2875
|
+
});
|
|
2876
|
+
};
|
|
2877
|
+
const onBatchUnarchive = async (target) => {
|
|
2878
|
+
if (busy) return;
|
|
2879
|
+
setBusy(true);
|
|
2880
|
+
setError(null);
|
|
2881
|
+
setNotice(null);
|
|
2882
|
+
try {
|
|
2883
|
+
const result = await unarchiveSessions(target);
|
|
2884
|
+
setNotice(t("archives.restoreSuccess", { n: result.unarchivedSessionIds.length }));
|
|
2885
|
+
} catch (reason) {
|
|
2886
|
+
setError(t("archives.restoreBatchFailed", { detail: reason instanceof Error ? reason.message : String(reason) }));
|
|
2887
|
+
} finally {
|
|
2888
|
+
setBusy(false);
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
const closeDelete = () => {
|
|
2892
|
+
if (!busy) setDeleteTarget(null);
|
|
2721
2893
|
};
|
|
2722
2894
|
(0, react.useEffect)(() => {
|
|
2723
2895
|
if (deleteTarget === null) return;
|
|
@@ -2732,49 +2904,61 @@ window.__ModuleLoader__.load({
|
|
|
2732
2904
|
return () => window.removeEventListener("keydown", onKeyDown, true);
|
|
2733
2905
|
}, [deleteTarget, busy]);
|
|
2734
2906
|
const confirmDelete = async () => {
|
|
2735
|
-
if (busy || deleteTarget === null) return;
|
|
2736
|
-
setBusy(true);
|
|
2737
|
-
setError(null);
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2907
|
+
if (busy || deleteTarget === null) return;
|
|
2908
|
+
setBusy(true);
|
|
2909
|
+
setError(null);
|
|
2910
|
+
setNotice(null);
|
|
2911
|
+
try {
|
|
2912
|
+
if (deleteTarget.kind === "batch") {
|
|
2913
|
+
const result = await deleteArchivedSessions(deleteTarget.target);
|
|
2914
|
+
const completed = result.deletedSessionIds.length + result.skippedSessionIds.length;
|
|
2915
|
+
if (result.failures.length > 0) {
|
|
2916
|
+
setError(t("archives.deletePartial", { done: completed, failed: result.failures.length, detail: result.failures[0].message }));
|
|
2917
|
+
} else {
|
|
2918
|
+
setNotice(t("archives.deleteSuccess", { n: completed }));
|
|
2919
|
+
}
|
|
2920
|
+
} else {
|
|
2921
|
+
await deleteSession(deleteTarget.session.id);
|
|
2922
|
+
}
|
|
2923
|
+
setDeleteTarget(null);
|
|
2748
2924
|
} catch (reason) {
|
|
2749
2925
|
setError(formatDeleteError(reason, t));
|
|
2750
|
-
} finally {
|
|
2751
|
-
setBusy(false);
|
|
2752
|
-
}
|
|
2753
|
-
};
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2926
|
+
} finally {
|
|
2927
|
+
setBusy(false);
|
|
2928
|
+
}
|
|
2929
|
+
};
|
|
2930
|
+
const batchScope = deleteTarget?.kind === "batch" ? deleteTarget.target.scope : null;
|
|
2931
|
+
const deleteDialogTitle = batchScope === "all" ? t("archives.deleteAllTitle") : batchScope === "ungrouped" ? t("archives.deleteUngroupedTitle") : batchScope === "workspace" ? t("archives.deleteProjectTitle", { name: deleteTarget.title }) : t("deleteSession.title");
|
|
2932
|
+
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 }) : t("deleteSession.desc", { name: displayTitle(deleteTarget.session, t) });
|
|
2933
|
+
const deleteConfirmLabel = batchScope === "all" ? t("archives.deleteAll") : batchScope === "ungrouped" ? t("archives.deleteUngroupedConfirm") : batchScope === "workspace" ? t("archives.deleteProjectConfirm") : t("deleteSession.title");
|
|
2934
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
2935
|
+
className: "dsham_settings",
|
|
2936
|
+
"aria-label": t("archives.title"),
|
|
2937
|
+
children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS }), (0, react_jsx_runtime.jsxs)("header", { className: "dsham_settingsHeader", children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (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) => {
|
|
2938
|
+
const target = archivedBatchTargetForGroup(group.key);
|
|
2939
|
+
const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
|
|
2940
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
2941
|
+
className: "dsham_settingsGroup",
|
|
2942
|
+
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", {
|
|
2943
|
+
className: "dsham_settingsList",
|
|
2761
2944
|
children: group.sessions.map((session) => (0, react_jsx_runtime.jsxs)("article", {
|
|
2762
2945
|
className: "dsham_settingsRow",
|
|
2763
2946
|
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", {
|
|
2764
2947
|
className: "dsham_settingsActions",
|
|
2765
|
-
children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy, onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy, "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget(session), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
|
|
2766
|
-
})]
|
|
2767
|
-
}, session.id))
|
|
2768
|
-
})]
|
|
2769
|
-
}, group.key)
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2948
|
+
children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy, onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy, "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
|
|
2949
|
+
})]
|
|
2950
|
+
}, session.id))
|
|
2951
|
+
})]
|
|
2952
|
+
}, group.key);
|
|
2953
|
+
}), 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, {
|
|
2954
|
+
open: deleteTarget !== null,
|
|
2955
|
+
onClose: closeDelete,
|
|
2956
|
+
closeLabel: t("close"),
|
|
2957
|
+
title: deleteDialogTitle,
|
|
2958
|
+
...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
|
|
2959
|
+
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", disabled: busy, onClick: confirmDelete, children: deleteConfirmLabel })] }),
|
|
2960
|
+
children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
|
|
2961
|
+
})]
|
|
2778
2962
|
});
|
|
2779
2963
|
}
|
|
2780
2964
|
//#endregion
|
|
@@ -2819,15 +3003,37 @@ window.__ModuleLoader__.load({
|
|
|
2819
3003
|
"archives.description": "管理已归档的会话。",
|
|
2820
3004
|
"archives.empty": "暂无已归档会话",
|
|
2821
3005
|
"archives.emptyFiltered": "没有匹配的已归档聊天",
|
|
2822
|
-
"archives.searchPlaceholder": "搜索已归档聊天",
|
|
2823
|
-
"archives.
|
|
3006
|
+
"archives.searchPlaceholder": "搜索已归档聊天",
|
|
3007
|
+
"archives.sortBy": "排序方式",
|
|
3008
|
+
"archives.sortUpdated": "更新时间",
|
|
3009
|
+
"archives.sortCreated": "创建时间",
|
|
3010
|
+
"archives.sortAlphabetical": "按字母顺序",
|
|
3011
|
+
"archives.projectFilter": "按项目筛选",
|
|
2824
3012
|
"archives.allProjects": "所有项目",
|
|
2825
|
-
"archives.sessionCount": "{n} 个聊天",
|
|
2826
|
-
"archives.timestamp": "{date},{time}",
|
|
2827
|
-
"archives.
|
|
2828
|
-
"archives.
|
|
2829
|
-
"archives.
|
|
2830
|
-
"archives.
|
|
3013
|
+
"archives.sessionCount": "{n} 个聊天",
|
|
3014
|
+
"archives.timestamp": "{date},{time}",
|
|
3015
|
+
"archives.restoreAll": "全部恢复",
|
|
3016
|
+
"archives.restoreProject": "恢复该项目的全部聊天",
|
|
3017
|
+
"archives.restoreUngrouped": "恢复未分组的全部聊天",
|
|
3018
|
+
"archives.deleteProject": "删除该项目的全部聊天",
|
|
3019
|
+
"archives.deleteUngrouped": "删除未分组的全部聊天",
|
|
3020
|
+
"archives.projectActions": "项目“{name}”的归档操作",
|
|
3021
|
+
"archives.ungroupedActions": "未分组聊天的归档操作",
|
|
3022
|
+
"archives.restoreSuccess": "已恢复 {n} 个已归档聊天。",
|
|
3023
|
+
"archives.restoreBatchFailed": "批量恢复失败:{detail}",
|
|
3024
|
+
"archives.deleteAll": "全部删除",
|
|
3025
|
+
"archives.deleteAllTitle": "删除全部已归档聊天",
|
|
3026
|
+
"archives.deleteAllDesc": "将永久删除全部 {n} 个已归档聊天及其子代理(含正在运行的)和记录,此操作不可恢复。",
|
|
3027
|
+
"archives.deleteAllPending": "正在删除已归档聊天…",
|
|
3028
|
+
"archives.deleteProjectTitle": "删除“{name}”中的已归档聊天",
|
|
3029
|
+
"archives.deleteProjectDesc": "将永久删除“{name}”中的 {n} 个已归档聊天及其子代理和记录。项目目录和未归档聊天不会受影响,此操作不可恢复。",
|
|
3030
|
+
"archives.deleteProjectConfirm": "删除该项目的全部聊天",
|
|
3031
|
+
"archives.deleteUngroupedTitle": "删除未分组的已归档聊天",
|
|
3032
|
+
"archives.deleteUngroupedDesc": "将永久删除未分组中的 {n} 个已归档聊天及其子代理和记录。其他项目和未归档聊天不会受影响,此操作不可恢复。",
|
|
3033
|
+
"archives.deleteUngroupedConfirm": "删除未分组的全部聊天",
|
|
3034
|
+
"archives.deleteBatchPending": "正在删除已归档聊天…",
|
|
3035
|
+
"archives.deleteSuccess": "已删除 {n} 个已归档聊天。",
|
|
3036
|
+
"archives.deletePartial": "已处理 {done} 个聊天,{failed} 个删除失败:{detail}",
|
|
2831
3037
|
"archives.unarchiveUnknown": "会话已不存在,无法取消归档。",
|
|
2832
3038
|
"archives.unarchiveFailed": "取消归档失败:{detail}",
|
|
2833
3039
|
"archives.archiveUnknown": "会话已不存在,无法归档。",
|
|
@@ -2911,15 +3117,37 @@ window.__ModuleLoader__.load({
|
|
|
2911
3117
|
"archives.description": "Manage archived sessions.",
|
|
2912
3118
|
"archives.empty": "No archived sessions.",
|
|
2913
3119
|
"archives.emptyFiltered": "No archived chats match your filters.",
|
|
2914
|
-
"archives.searchPlaceholder": "Search archived chats",
|
|
2915
|
-
"archives.
|
|
3120
|
+
"archives.searchPlaceholder": "Search archived chats",
|
|
3121
|
+
"archives.sortBy": "Sort archived chats",
|
|
3122
|
+
"archives.sortUpdated": "Last updated",
|
|
3123
|
+
"archives.sortCreated": "Created",
|
|
3124
|
+
"archives.sortAlphabetical": "Alphabetical",
|
|
3125
|
+
"archives.projectFilter": "Filter by project",
|
|
2916
3126
|
"archives.allProjects": "All projects",
|
|
2917
|
-
"archives.sessionCount": "{n} chats",
|
|
2918
|
-
"archives.timestamp": "{date}, {time}",
|
|
2919
|
-
"archives.
|
|
2920
|
-
"archives.
|
|
2921
|
-
"archives.
|
|
2922
|
-
"archives.
|
|
3127
|
+
"archives.sessionCount": "{n} chats",
|
|
3128
|
+
"archives.timestamp": "{date}, {time}",
|
|
3129
|
+
"archives.restoreAll": "Restore all",
|
|
3130
|
+
"archives.restoreProject": "Restore all chats in this project",
|
|
3131
|
+
"archives.restoreUngrouped": "Restore all ungrouped chats",
|
|
3132
|
+
"archives.deleteProject": "Delete all chats in this project",
|
|
3133
|
+
"archives.deleteUngrouped": "Delete all ungrouped chats",
|
|
3134
|
+
"archives.projectActions": "Archive actions for project {name}",
|
|
3135
|
+
"archives.ungroupedActions": "Archive actions for ungrouped chats",
|
|
3136
|
+
"archives.restoreSuccess": "Restored {n} archived chats.",
|
|
3137
|
+
"archives.restoreBatchFailed": "Could not restore the archived chats: {detail}",
|
|
3138
|
+
"archives.deleteAll": "Delete all",
|
|
3139
|
+
"archives.deleteAllTitle": "Delete all archived chats",
|
|
3140
|
+
"archives.deleteAllDesc": "This permanently deletes all {n} archived chats, their child agents (including any that are still running), and their records. This cannot be undone.",
|
|
3141
|
+
"archives.deleteAllPending": "Deleting archived chats…",
|
|
3142
|
+
"archives.deleteProjectTitle": "Delete archived chats in {name}",
|
|
3143
|
+
"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.",
|
|
3144
|
+
"archives.deleteProjectConfirm": "Delete all project chats",
|
|
3145
|
+
"archives.deleteUngroupedTitle": "Delete ungrouped archived chats",
|
|
3146
|
+
"archives.deleteUngroupedDesc": "This permanently deletes the {n} ungrouped archived chats, their child agents, and their records. Other projects and unarchived chats are not affected. This cannot be undone.",
|
|
3147
|
+
"archives.deleteUngroupedConfirm": "Delete all ungrouped chats",
|
|
3148
|
+
"archives.deleteBatchPending": "Deleting archived chats…",
|
|
3149
|
+
"archives.deleteSuccess": "Deleted {n} archived chats.",
|
|
3150
|
+
"archives.deletePartial": "Processed {done} chats; {failed} could not be deleted: {detail}",
|
|
2923
3151
|
"archives.unarchiveUnknown": "This session no longer exists, so it cannot be unarchived.",
|
|
2924
3152
|
"archives.unarchiveFailed": "Could not unarchive the session: {detail}",
|
|
2925
3153
|
"archives.archiveUnknown": "This session no longer exists, so it cannot be archived.",
|
|
@@ -3034,12 +3262,33 @@ window.__ModuleLoader__.load({
|
|
|
3034
3262
|
const result = await registry.unarchiveSession(sessionId);
|
|
3035
3263
|
if (!result.ok) throw new Error(result.error.message);
|
|
3036
3264
|
};
|
|
3037
|
-
const deleteSession = async (sessionId) => {
|
|
3038
|
-
const registry = ctx.get("remote.workspaceRegistry");
|
|
3039
|
-
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
3040
|
-
const result = await registry.deleteSession(sessionId);
|
|
3041
|
-
if (!result.ok) throw new Error(result.error.message);
|
|
3042
|
-
};
|
|
3265
|
+
const deleteSession = async (sessionId) => {
|
|
3266
|
+
const registry = ctx.get("remote.workspaceRegistry");
|
|
3267
|
+
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
3268
|
+
const result = await registry.deleteSession(sessionId);
|
|
3269
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
3270
|
+
};
|
|
3271
|
+
const unarchiveSessions = async (target) => {
|
|
3272
|
+
const registry = ctx.get("remote.workspaceRegistry");
|
|
3273
|
+
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
3274
|
+
const result = await registry.unarchiveSessions(target);
|
|
3275
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
3276
|
+
return result.value;
|
|
3277
|
+
};
|
|
3278
|
+
const deleteArchivedSessions = async (target) => {
|
|
3279
|
+
const registry = ctx.get("remote.workspaceRegistry");
|
|
3280
|
+
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
3281
|
+
const result = await registry.deleteArchivedSessions(target);
|
|
3282
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
3283
|
+
return result.value;
|
|
3284
|
+
};
|
|
3285
|
+
const archivedSessionMetadata = async () => {
|
|
3286
|
+
const registry = ctx.get("remote.workspaceRegistry");
|
|
3287
|
+
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
3288
|
+
const result = await registry.archivedSessionMetadata();
|
|
3289
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
3290
|
+
return result.value;
|
|
3291
|
+
};
|
|
3043
3292
|
const browserInjected = () => ({
|
|
3044
3293
|
startSession: (workspaceId) => {
|
|
3045
3294
|
ctx.workspaces.startSession(workspaceId);
|
|
@@ -3113,10 +3362,13 @@ window.__ModuleLoader__.load({
|
|
|
3113
3362
|
locale: NS,
|
|
3114
3363
|
inject: () => ({
|
|
3115
3364
|
sessionStore: ctx.sessions.list,
|
|
3116
|
-
workspaceStore: ctx.workspaces.list,
|
|
3117
|
-
unarchiveSession,
|
|
3118
|
-
deleteSession,
|
|
3119
|
-
|
|
3365
|
+
workspaceStore: ctx.workspaces.list,
|
|
3366
|
+
unarchiveSession,
|
|
3367
|
+
deleteSession,
|
|
3368
|
+
unarchiveSessions,
|
|
3369
|
+
deleteArchivedSessions,
|
|
3370
|
+
archivedSessionMetadata,
|
|
3371
|
+
t: ctx.locale.bind(NS)
|
|
3120
3372
|
})
|
|
3121
3373
|
}, ArchivedSessionsSection));
|
|
3122
3374
|
}
|
|
@@ -3128,10 +3380,11 @@ window.__ModuleLoader__.load({
|
|
|
3128
3380
|
isUnknownSessionError,
|
|
3129
3381
|
deriveGroups,
|
|
3130
3382
|
deriveFlat,
|
|
3131
|
-
deriveSearchResults,
|
|
3132
|
-
deriveArchivedGroups,
|
|
3133
|
-
|
|
3134
|
-
|
|
3383
|
+
deriveSearchResults,
|
|
3384
|
+
deriveArchivedGroups,
|
|
3385
|
+
sortArchivedGroups,
|
|
3386
|
+
deriveArchivedBatchIds,
|
|
3387
|
+
createWorkspaceViewStore,
|
|
3135
3388
|
groupByWorkspace,
|
|
3136
3389
|
byRecency,
|
|
3137
3390
|
ARCHIVE_MANAGER_REMOTE
|
package/lib/workspace.js
CHANGED
|
@@ -24,7 +24,8 @@ import { trackTombstone } from "./tombstone.js";
|
|
|
24
24
|
* 墓碑同时记录被删生命周期的日志身份(createdAt/cwd):其他进程以同 id
|
|
25
25
|
* 重建并落盘新会话时(冷复用),sessionKnown 的探针以身份差异区分新旧
|
|
26
26
|
* 生命周期并撤掉墓碑,避免已重建的会话在本进程永久 UNKNOWN_SESSION。
|
|
27
|
-
*
|
|
27
|
+
* 单会话与批量恢复/删除方法均通过 Typert Remote 暴露给浏览器,并注册到
|
|
28
|
+
* typert.local,避免生产环境只靠 SRC 扫描时 404。
|
|
28
29
|
*/
|
|
29
30
|
function markRemoteMethod(instance, method) {
|
|
30
31
|
// 模拟 TS 装饰器管线 `@Remote(method)`:`Remote` 返回标准方法装饰器,
|
|
@@ -71,12 +72,45 @@ const archivedSetSchema = {
|
|
|
71
72
|
return value;
|
|
72
73
|
}
|
|
73
74
|
};
|
|
74
|
-
const deletedSchema = {
|
|
75
|
-
parse(value) {
|
|
76
|
-
if (typeof value !== "object" || value === null || Array.isArray(value) || value.deleted !== true) throw new TypeError("deleted must be true");
|
|
77
|
-
return value;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
75
|
+
const deletedSchema = {
|
|
76
|
+
parse(value) {
|
|
77
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value.deleted !== true) throw new TypeError("deleted must be true");
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const archivedBatchTargetSchema = {
|
|
82
|
+
parse(value) {
|
|
83
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("target must be an object");
|
|
84
|
+
if (value.scope === "all" || value.scope === "ungrouped") return value;
|
|
85
|
+
if (value.scope === "workspace" && typeof value.workspaceId === "string" && value.workspaceId.length > 0) return value;
|
|
86
|
+
throw new TypeError("target.scope must be all, ungrouped, or workspace with a non-empty workspaceId");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const unarchivedBatchSchema = {
|
|
90
|
+
parse(value) {
|
|
91
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
92
|
+
if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIds must be a string array");
|
|
93
|
+
if (!Array.isArray(value.unarchivedSessionIds) || value.unarchivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("unarchivedSessionIds must be a string array");
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const deletedBatchSchema = {
|
|
98
|
+
parse(value) {
|
|
99
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
100
|
+
for (const key of ["requestedSessionIds", "deletedSessionIds", "skippedSessionIds"]) {
|
|
101
|
+
if (!Array.isArray(value[key]) || value[key].some((id) => typeof id !== "string")) throw new TypeError(`${key} must be a string array`);
|
|
102
|
+
}
|
|
103
|
+
if (!Array.isArray(value.failures) || value.failures.some((failure) => typeof failure !== "object" || failure === null || typeof failure.sessionId !== "string" || typeof failure.message !== "string")) throw new TypeError("failures must contain sessionId/message objects");
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const archivedSessionMetadataSchema = {
|
|
108
|
+
parse(value) {
|
|
109
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !Array.isArray(value.items)) throw new TypeError("result.items must be an array");
|
|
110
|
+
if (value.items.some((item) => typeof item !== "object" || item === null || typeof item.sessionId !== "string" || typeof item.createdAt !== "number" || !Number.isFinite(item.createdAt))) throw new TypeError("items must contain sessionId/createdAt objects");
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
80
114
|
/**
|
|
81
115
|
* Host 严格描述符。网关优先读 typert.local,避免 SRC 扫描缓存
|
|
82
116
|
* 或协议包双份导致 /api/workspaceRegistry/deleteSession 在生产环境 404。
|
|
@@ -101,8 +135,8 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
|
|
|
101
135
|
},
|
|
102
136
|
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
103
137
|
},
|
|
104
|
-
{
|
|
105
|
-
id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteSession",
|
|
138
|
+
{
|
|
139
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteSession",
|
|
106
140
|
service: "workspaceRegistry",
|
|
107
141
|
namespace: "workspaceRegistry",
|
|
108
142
|
method: "deleteSession",
|
|
@@ -117,10 +151,62 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
|
|
|
117
151
|
mode: "strict",
|
|
118
152
|
typeSymbol: "@michengai/dsh-archive-manager/types#Deleted",
|
|
119
153
|
schema: deletedSchema
|
|
120
|
-
},
|
|
121
|
-
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
122
|
-
}
|
|
123
|
-
|
|
154
|
+
},
|
|
155
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/unarchiveSessions",
|
|
159
|
+
service: "workspaceRegistry",
|
|
160
|
+
namespace: "workspaceRegistry",
|
|
161
|
+
method: "unarchiveSessions",
|
|
162
|
+
invocation: { kind: "direct" },
|
|
163
|
+
parameters: [{
|
|
164
|
+
name: "target",
|
|
165
|
+
wire: "target",
|
|
166
|
+
source: "json",
|
|
167
|
+
codec: { mode: "strict", typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget", schema: archivedBatchTargetSchema }
|
|
168
|
+
}],
|
|
169
|
+
result: {
|
|
170
|
+
mode: "strict",
|
|
171
|
+
typeSymbol: "@michengai/dsh-archive-manager/types#UnarchivedBatch",
|
|
172
|
+
schema: unarchivedBatchSchema
|
|
173
|
+
},
|
|
174
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteArchivedSessions",
|
|
178
|
+
service: "workspaceRegistry",
|
|
179
|
+
namespace: "workspaceRegistry",
|
|
180
|
+
method: "deleteArchivedSessions",
|
|
181
|
+
invocation: { kind: "direct" },
|
|
182
|
+
parameters: [{
|
|
183
|
+
name: "target",
|
|
184
|
+
wire: "target",
|
|
185
|
+
source: "json",
|
|
186
|
+
codec: { mode: "strict", typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget", schema: archivedBatchTargetSchema }
|
|
187
|
+
}],
|
|
188
|
+
result: {
|
|
189
|
+
mode: "strict",
|
|
190
|
+
typeSymbol: "@michengai/dsh-archive-manager/types#DeletedBatch",
|
|
191
|
+
schema: deletedBatchSchema
|
|
192
|
+
},
|
|
193
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/archivedSessionMetadata",
|
|
197
|
+
service: "workspaceRegistry",
|
|
198
|
+
namespace: "workspaceRegistry",
|
|
199
|
+
method: "archivedSessionMetadata",
|
|
200
|
+
invocation: { kind: "direct" },
|
|
201
|
+
parameters: [],
|
|
202
|
+
result: {
|
|
203
|
+
mode: "strict",
|
|
204
|
+
typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedSessionMetadata",
|
|
205
|
+
schema: archivedSessionMetadataSchema
|
|
206
|
+
},
|
|
207
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
208
|
+
}
|
|
209
|
+
];
|
|
124
210
|
const ARCHIVE_MANAGER_TYPERT = {
|
|
125
211
|
package: "@michengai/dsh-archive-manager",
|
|
126
212
|
face: "host",
|
|
@@ -155,11 +241,27 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
155
241
|
deletedIdentities = /* @__PURE__ */ new Map();
|
|
156
242
|
constructor(ctx) {
|
|
157
243
|
super(ctx);
|
|
158
|
-
this.typertRemote = bindTypertRemote(this, this.name);
|
|
159
|
-
markRemoteMethod(this, "unarchiveSession");
|
|
160
|
-
markRemoteMethod(this, "deleteSession");
|
|
161
|
-
|
|
162
|
-
|
|
244
|
+
this.typertRemote = bindTypertRemote(this, this.name);
|
|
245
|
+
markRemoteMethod(this, "unarchiveSession");
|
|
246
|
+
markRemoteMethod(this, "deleteSession");
|
|
247
|
+
markRemoteMethod(this, "unarchiveSessions");
|
|
248
|
+
markRemoteMethod(this, "deleteArchivedSessions");
|
|
249
|
+
markRemoteMethod(this, "archivedSessionMetadata");
|
|
250
|
+
registerHostRemote(this.ctx);
|
|
251
|
+
}
|
|
252
|
+
/** 归档设置页创建时间排序所需的最小元数据;列表摘要本身只暴露 updatedAt。 */
|
|
253
|
+
async archivedSessionMetadata() {
|
|
254
|
+
const items = [];
|
|
255
|
+
for (const sessionId of [...new Set(this.requireState().archivedSessionIds)]) {
|
|
256
|
+
try {
|
|
257
|
+
const header = await this.readSessionHeader(sessionId);
|
|
258
|
+
if (typeof header.createdAt === "number" && Number.isFinite(header.createdAt)) items.push({ sessionId, createdAt: header.createdAt });
|
|
259
|
+
} catch (error) {
|
|
260
|
+
this.ctx.logger.warn(`archive-manager: could not read creation time for archived session "${sessionId}": ${String(error)}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return { items };
|
|
264
|
+
}
|
|
163
265
|
/**
|
|
164
266
|
* 把一个会话移出注册表全局归档集合,恢复其正常可见性(其记账位从未
|
|
165
267
|
* 移动,会话在原工作区位置重新出现)。幂等:未归档的已知会话直接返回
|
|
@@ -167,7 +269,7 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
167
269
|
* @param sessionId - 要取消归档的会话。
|
|
168
270
|
* @returns 更新后的完整归档集合。
|
|
169
271
|
*/
|
|
170
|
-
async unarchiveSession(sessionId) {
|
|
272
|
+
async unarchiveSession(sessionId) {
|
|
171
273
|
return this.enqueueOperation(async () => {
|
|
172
274
|
if (!await this.sessionKnown(sessionId)) throw new ArchiveUnknownSessionError(sessionId);
|
|
173
275
|
const state = this.requireState();
|
|
@@ -178,8 +280,76 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
178
280
|
};
|
|
179
281
|
await this.setState(next);
|
|
180
282
|
return { archivedSessionIds: [...next.archivedSessionIds] };
|
|
181
|
-
});
|
|
182
|
-
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 按宿主权威归档集合一次恢复全部、一个工作区或未分组的归档会话。
|
|
287
|
+
* 目标全部来自已归档集合,因此即使日志已被外部移除,也会清掉陈旧归档标记。
|
|
288
|
+
*/
|
|
289
|
+
async unarchiveSessions(target) {
|
|
290
|
+
return this.enqueueOperation(async () => {
|
|
291
|
+
const unarchivedSessionIds = this.archivedSessionIdsForTarget(target);
|
|
292
|
+
if (unarchivedSessionIds.length === 0) return {
|
|
293
|
+
archivedSessionIds: [...this.requireState().archivedSessionIds],
|
|
294
|
+
unarchivedSessionIds: []
|
|
295
|
+
};
|
|
296
|
+
const restored = new Set(unarchivedSessionIds);
|
|
297
|
+
const state = this.requireState();
|
|
298
|
+
const next = {
|
|
299
|
+
...state,
|
|
300
|
+
archivedSessionIds: state.archivedSessionIds.filter((id) => !restored.has(id))
|
|
301
|
+
};
|
|
302
|
+
await this.setState(next);
|
|
303
|
+
return {
|
|
304
|
+
archivedSessionIds: [...next.archivedSessionIds],
|
|
305
|
+
unarchivedSessionIds
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 按作用域永久删除归档会话。跨会话文件删除无法组成事务,因此继续处理
|
|
311
|
+
* 后续目标并把成功、并发消失和失败分别返回给客户端。
|
|
312
|
+
*/
|
|
313
|
+
async deleteArchivedSessions(target) {
|
|
314
|
+
return this.enqueueOperation(async () => {
|
|
315
|
+
const requestedSessionIds = this.archivedSessionIdsForTarget(target);
|
|
316
|
+
const deletedSessionIds = [];
|
|
317
|
+
const skippedSessionIds = [];
|
|
318
|
+
const failures = [];
|
|
319
|
+
for (const sessionId of requestedSessionIds) {
|
|
320
|
+
try {
|
|
321
|
+
await this.deleteSessionCore(sessionId);
|
|
322
|
+
deletedSessionIds.push(sessionId);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (error instanceof ArchiveUnknownSessionError) {
|
|
325
|
+
skippedSessionIds.push(sessionId);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
failures.push({ sessionId, message: String(error) });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { requestedSessionIds, deletedSessionIds, skippedSessionIds, failures };
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
/** 以归档集合顺序解析批量目标,避免依赖浏览器尚未加载完整的摘要投影。 */
|
|
335
|
+
archivedSessionIdsForTarget(target) {
|
|
336
|
+
target = archivedBatchTargetSchema.parse(target);
|
|
337
|
+
const state = this.requireState();
|
|
338
|
+
const archivedSessionIds = [...new Set(state.archivedSessionIds)];
|
|
339
|
+
if (target.scope === "all") return archivedSessionIds;
|
|
340
|
+
if (target.scope === "workspace") {
|
|
341
|
+
const workspace = this.requireTable().get(target.workspaceId);
|
|
342
|
+
if (workspace === void 0) throw new Error(`unknown workspace "${target.workspaceId}"`);
|
|
343
|
+
const accounted = new Set(workspace.sessionIds);
|
|
344
|
+
return archivedSessionIds.filter((id) => accounted.has(id));
|
|
345
|
+
}
|
|
346
|
+
const accounted = /* @__PURE__ */ new Set();
|
|
347
|
+
const table = this.requireTable();
|
|
348
|
+
for (const workspaceId of state.workspaceIds) {
|
|
349
|
+
for (const sessionId of table.get(workspaceId)?.sessionIds ?? []) accounted.add(sessionId);
|
|
350
|
+
}
|
|
351
|
+
return archivedSessionIds.filter((id) => !accounted.has(id));
|
|
352
|
+
}
|
|
183
353
|
/**
|
|
184
354
|
* 永久删除一个会话及其全部痕迹(转录目录、工作区记账、归档标记、
|
|
185
355
|
* 投影缓存行)。
|