@michengai/dsh-archive-manager 0.1.23 → 0.1.25
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 +13 -0
- package/CHANGELOG.zh-CN.md +13 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/lib/client.js +185 -26
- package/lib/projcache.js +100 -26
- package/lib/workspace.js +80 -3
- package/package.json +31 -14
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
Published release notes are retained below; new versions are added without removing earlier entries.
|
|
6
6
|
|
|
7
|
+
## 0.1.25 — 2026-09-03
|
|
8
|
+
|
|
9
|
+
- Added **Archive all chats** to each workspace action menu. The entry is hidden when no active chats remain, and the client refreshes its session list after a successful batch archive.
|
|
10
|
+
- Added a red outlined confirmation style for **Archive all** with hover and keyboard-focus states.
|
|
11
|
+
- Kept the path-safe `session_projcache_archive_manager_v2` domain as the only write target. Startup now imports missing entries from the short-lived old-name v2 domain, old-name v1 domain, and supported DSH cache formats without overwriting newer records.
|
|
12
|
+
- Added RC2-to-current isolated compatibility coverage and made it a release gate: CI and `prepublishOnly` now run `test:compat`; CI no longer assumes pnpm exists before Corepack enables it.
|
|
13
|
+
|
|
14
|
+
## 0.1.24 — 2026-09-02
|
|
15
|
+
|
|
16
|
+
- Upgraded the development test stack to DSH `0.1.2-alpha.5` and `@deepseek-ai/cordis` `4.0.2`, while retaining the existing `>=0.1.0-rc.5 <0.2.0` DSH runtime peer declarations.
|
|
17
|
+
- Switched primary client integration tests to the current `dsh-client-store` contract and retained a tested optional `dsh-client-runtime` fallback for older hosts.
|
|
18
|
+
- Added a reproducible pnpm alpha-resolution policy that disables automatic peer installation and records the approved newly published packages.
|
|
19
|
+
|
|
7
20
|
## 0.1.23 — 2026-09-01
|
|
8
21
|
|
|
9
22
|
- Fixed missing sidebar pending-state indicators on DSH `v0.1.2-alpha.2`: workspace rows now read pending UI interactions from the `ui-session` `pendingInteractions` Map.
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
以下发布说明会持续保留;新增版本时不再删除较早记录。
|
|
6
6
|
|
|
7
|
+
## 0.1.25 — 2026-09-03
|
|
8
|
+
|
|
9
|
+
- 每个工作区操作菜单新增「归档全部聊天」;没有可归档聊天时不显示入口,批量归档成功后立即刷新客户端会话列表。
|
|
10
|
+
- 「全部归档」确认按钮使用红色描边,并提供悬停和键盘焦点状态。
|
|
11
|
+
- 路径安全的 `session_projcache_archive_manager_v2` 域继续作为唯一写入目标;启动时从短暂使用过的旧名 v2 域、旧名 v1 域及受支持的 DSH 缓存格式只读补缺导入,不覆盖更新的记录。
|
|
12
|
+
- 新增从 RC2 到当前版本的隔离兼容回归,并将其纳入发布门禁:CI 与 `prepublishOnly` 均执行 `test:compat`,CI 不再假定 Corepack 启用前系统已存在 pnpm。
|
|
13
|
+
|
|
14
|
+
## 0.1.24 — 2026-09-02
|
|
15
|
+
|
|
16
|
+
- 将开发测试依赖升级至 DSH `0.1.2-alpha.5` 与 `@deepseek-ai/cordis` `4.0.2`,同时保留原有的 DSH 运行时 peer 声明 `>=0.1.0-rc.5 <0.2.0`。
|
|
17
|
+
- 客户端集成测试以当前 `dsh-client-store` 为主路径,并保留经过测试的可选 `dsh-client-runtime` 回退,兼容旧版宿主。
|
|
18
|
+
- 新增可复现的 pnpm alpha 解析策略:关闭自动安装 peer,并记录已批准的刚发布依赖。
|
|
19
|
+
|
|
7
20
|
## 0.1.23 — 2026-09-01
|
|
8
21
|
|
|
9
22
|
- 修复 DSH `v0.1.2-alpha.2` 后会话侧栏“等待回答 / 等待审批”状态点不显示的问题:侧栏行现在从 `ui-session` 的 `pendingInteractions` Map 读取待处理交互状态。
|
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
24
|
- Archive a session from the sidebar session menu.
|
|
25
|
+
- Archive every active chat in a workspace from its sidebar action menu.
|
|
25
26
|
- Search archived chats, sort them by update time, creation time, or title, and filter them by workspace in **Settings → Archived**.
|
|
26
27
|
- Restore a session to its original workspace with **Unarchive**.
|
|
27
28
|
- Restore or permanently delete every archived chat in a project group.
|
package/README.zh-CN.md
CHANGED
package/lib/client.js
CHANGED
|
@@ -30,6 +30,12 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
return value;
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
+
const workspaceIdSchema = {
|
|
34
|
+
parse(value) {
|
|
35
|
+
if (typeof value !== "string" || value.length === 0) throw new TypeError(`workspaceId must be a non-empty string, got ${String(value)}`);
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
33
39
|
const archivedSetSchema = {
|
|
34
40
|
parse(value) {
|
|
35
41
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
@@ -61,6 +67,14 @@ window.__ModuleLoader__.load({
|
|
|
61
67
|
return value;
|
|
62
68
|
}
|
|
63
69
|
};
|
|
70
|
+
const archivedWorkspaceBatchSchema = {
|
|
71
|
+
parse(value) {
|
|
72
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
73
|
+
if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIds must be a string array");
|
|
74
|
+
if (!Array.isArray(value.archivedSessionIdsAdded) || value.archivedSessionIdsAdded.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIdsAdded must be a string array");
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
64
78
|
const deletedBatchSchema = {
|
|
65
79
|
parse(value) {
|
|
66
80
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
@@ -143,6 +157,25 @@ window.__ModuleLoader__.load({
|
|
|
143
157
|
},
|
|
144
158
|
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
145
159
|
},
|
|
160
|
+
{
|
|
161
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveWorkspaceSessions",
|
|
162
|
+
service: "workspaceRegistry",
|
|
163
|
+
namespace: "workspaceRegistry",
|
|
164
|
+
method: "archiveWorkspaceSessions",
|
|
165
|
+
invocation: { kind: "direct" },
|
|
166
|
+
parameters: [{
|
|
167
|
+
name: "workspaceId",
|
|
168
|
+
wire: "workspaceId",
|
|
169
|
+
source: "json",
|
|
170
|
+
codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-workspace/types#WorkspaceId", schema: workspaceIdSchema }
|
|
171
|
+
}],
|
|
172
|
+
result: {
|
|
173
|
+
mode: "strict",
|
|
174
|
+
typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedWorkspaceBatch",
|
|
175
|
+
schema: archivedWorkspaceBatchSchema
|
|
176
|
+
},
|
|
177
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
178
|
+
},
|
|
146
179
|
{
|
|
147
180
|
id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteArchivedSessions",
|
|
148
181
|
service: "workspaceRegistry",
|
|
@@ -284,6 +317,10 @@ window.__ModuleLoader__.load({
|
|
|
284
317
|
}
|
|
285
318
|
});
|
|
286
319
|
}
|
|
320
|
+
/** GitHub 品牌标识未由宿主图标库提供,内联以保持离线可用和主题适配。 */
|
|
321
|
+
function GithubMark16() {
|
|
322
|
+
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" }) });
|
|
323
|
+
}
|
|
287
324
|
/**
|
|
288
325
|
* 归档多选原型。保留既有设置页作为兼容回退,原型直接使用相同宿主
|
|
289
326
|
* 批量接口,因此其恢复、删除和并发安全语义与原有项目级操作一致。
|
|
@@ -423,10 +460,10 @@ window.__ModuleLoader__.load({
|
|
|
423
460
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
424
461
|
className: "dsham_settings",
|
|
425
462
|
"aria-label": t("archives.title"),
|
|
426
|
-
children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS + ARCHIVE_SETTINGS_SELECTION_CSS }), (0, react_jsx_runtime.jsxs)("header", {
|
|
463
|
+
children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS + ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS + ARCHIVE_SETTINGS_SELECTION_CSS + ARCHIVE_SETTINGS_DELETE_CONFIRM_CSS }), (0, react_jsx_runtime.jsxs)("header", {
|
|
427
464
|
className: "dsham_settingsHeader",
|
|
428
465
|
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
429
|
-
|
|
466
|
+
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") })]
|
|
430
467
|
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
431
468
|
className: "dsham_settingsHeaderActions",
|
|
432
469
|
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")] })]
|
|
@@ -468,7 +505,7 @@ window.__ModuleLoader__.load({
|
|
|
468
505
|
closeLabel: t("close"),
|
|
469
506
|
title: deleteDialogTitle,
|
|
470
507
|
...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
|
|
471
|
-
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 })] }),
|
|
508
|
+
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 })] }),
|
|
472
509
|
children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
|
|
473
510
|
})]
|
|
474
511
|
});
|
|
@@ -496,8 +533,8 @@ window.__ModuleLoader__.load({
|
|
|
496
533
|
* @param cwd - directory path, or undefined for the ungrouped bucket.
|
|
497
534
|
* @returns basename, the raw cwd when it has no basename, or the ungrouped label.
|
|
498
535
|
*/
|
|
499
|
-
function workspaceLabel(cwd) {
|
|
500
|
-
if (cwd === void 0 || cwd === "") return
|
|
536
|
+
function workspaceLabel(cwd, ungroupedLabel = UNGROUPED_LABEL) {
|
|
537
|
+
if (cwd === void 0 || cwd === "") return ungroupedLabel;
|
|
501
538
|
const base = cwd.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
|
|
502
539
|
return base !== void 0 && base !== "" ? base : cwd;
|
|
503
540
|
}
|
|
@@ -603,6 +640,22 @@ window.__ModuleLoader__.load({
|
|
|
603
640
|
if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, UNGROUPED_LABEL, ungroupedOrder === void 0 ? stray : orderedUngrouped(stray, ungroupedOrder), ungroupedOrder === void 0 ? "recency" : "account"));
|
|
604
641
|
return groups;
|
|
605
642
|
}
|
|
643
|
+
/** 返回工作区中尚未归档的唯一会话数量。 */
|
|
644
|
+
function archiveableWorkspaceSessionCount(workspace, archivedSessionIds) {
|
|
645
|
+
const archived = archivedSessionIds instanceof Set ? archivedSessionIds : new Set(archivedSessionIds);
|
|
646
|
+
return [...new Set(workspace.sessionIds)].filter((sessionId) => !archived.has(sessionId)).length;
|
|
647
|
+
}
|
|
648
|
+
/** 仅在工作区仍有活跃会话时创建批量归档确认目标。 */
|
|
649
|
+
function archiveWorkspaceDialogTarget(workspaces, workspaceId, title, archivedSessionIds) {
|
|
650
|
+
const workspace = workspaces.find((item) => item.workspaceId === workspaceId);
|
|
651
|
+
if (workspace === void 0) return null;
|
|
652
|
+
const count = archiveableWorkspaceSessionCount(workspace, archivedSessionIds);
|
|
653
|
+
return count === 0 ? null : { workspaceId, title, count };
|
|
654
|
+
}
|
|
655
|
+
/** 请求失败时保留确认目标,方便用户查看错误后重试。 */
|
|
656
|
+
function archiveWorkspaceDialogFailureState(target, reason, t) {
|
|
657
|
+
return { target, archiving: false, error: formatArchiveError(reason, t) };
|
|
658
|
+
}
|
|
606
659
|
/** 只把侧栏已支持的待处理交互类型交给行渲染,避免未知类型触发断言。 */
|
|
607
660
|
function visiblePendingKind(kind) {
|
|
608
661
|
switch (kind) {
|
|
@@ -711,9 +764,10 @@ window.__ModuleLoader__.load({
|
|
|
711
764
|
* @param content - ranked Host content-search page.
|
|
712
765
|
* @param limit - protocol-owned maximum merged row count.
|
|
713
766
|
* @param showArchived - "show archived" view toggle.
|
|
767
|
+
* @param ungroupedLabel - 未归属工作区会话的本地化回退标签。
|
|
714
768
|
* @returns bounded deduplicated flat rows and a refine-query hint bit.
|
|
715
769
|
*/
|
|
716
|
-
function deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, content, limit, showArchived) {
|
|
770
|
+
function deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, content, limit, showArchived, ungroupedLabel = UNGROUPED_LABEL) {
|
|
717
771
|
const q = query.trim().toLowerCase();
|
|
718
772
|
if (q === "") return {
|
|
719
773
|
items: [],
|
|
@@ -723,7 +777,7 @@ window.__ModuleLoader__.load({
|
|
|
723
777
|
const descendants = indexSubagentDescendants(list.byId);
|
|
724
778
|
const workspaceBySession = /* @__PURE__ */ new Map();
|
|
725
779
|
for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
|
|
726
|
-
const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
|
|
780
|
+
const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd, ungroupedLabel);
|
|
727
781
|
const contentBySession = /* @__PURE__ */ new Map();
|
|
728
782
|
for (const item of content.items) if (!contentBySession.has(item.sessionId)) contentBySession.set(item.sessionId, item);
|
|
729
783
|
const local = [];
|
|
@@ -937,7 +991,11 @@ window.__ModuleLoader__.load({
|
|
|
937
991
|
id: "rename",
|
|
938
992
|
label: t("rename"),
|
|
939
993
|
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
|
|
940
|
-
}, {
|
|
994
|
+
}, ...(actions?.canArchive === true ? [{
|
|
995
|
+
id: "archive-workspace",
|
|
996
|
+
label: t("menu.archiveWorkspace"),
|
|
997
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
|
|
998
|
+
}] : []), {
|
|
941
999
|
id: "delete",
|
|
942
1000
|
label: t("delete.workspace"),
|
|
943
1001
|
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
|
|
@@ -981,9 +1039,10 @@ window.__ModuleLoader__.load({
|
|
|
981
1039
|
items: workspaceMenuItems,
|
|
982
1040
|
onSelect: (id) => {
|
|
983
1041
|
setMenuOpen(false);
|
|
984
|
-
/* v8 ignore next -- workspaceMenuItems carries exactly these
|
|
985
|
-
if (id !== "rename" && id !== "delete") return;
|
|
1042
|
+
/* v8 ignore next -- workspaceMenuItems carries exactly these three rows today. */
|
|
1043
|
+
if (id !== "rename" && id !== "archive-workspace" && id !== "delete") return;
|
|
986
1044
|
if (id === "rename") actions.rename();
|
|
1045
|
+
else if (id === "archive-workspace") actions.archive();
|
|
987
1046
|
else actions.delete();
|
|
988
1047
|
},
|
|
989
1048
|
portal: true,
|
|
@@ -1736,7 +1795,7 @@ window.__ModuleLoader__.load({
|
|
|
1736
1795
|
return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
1737
1796
|
}
|
|
1738
1797
|
/** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
|
|
1739
|
-
function SessionTree({ useSessions, useSessionPendingInteraction, startSession, open, forkSession, workspaces, archivedSessionIds, showArchived, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
|
|
1798
|
+
function SessionTree({ useSessions, useSessionPendingInteraction, startSession, open, forkSession, workspaces, archivedSessionIds, showArchived, onRenameRequest, onArchiveRequest, onDeleteRequest, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
|
|
1740
1799
|
const list = useSessions((s) => s);
|
|
1741
1800
|
const pendingInteractions = useSessionPendingInteraction((s) => s);
|
|
1742
1801
|
const current = list.current;
|
|
@@ -1932,16 +1991,24 @@ window.__ModuleLoader__.load({
|
|
|
1932
1991
|
}
|
|
1933
1992
|
},
|
|
1934
1993
|
drag: workspaceDragProps,
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1994
|
+
actions: group.workspaceId === void 0 ? void 0 : {
|
|
1995
|
+
canArchive: (() => {
|
|
1996
|
+
const workspace = workspaces.find((item) => item.workspaceId === group.workspaceId);
|
|
1997
|
+
return workspace !== void 0 && archiveableWorkspaceSessionCount(workspace, archivedSessionIds) > 0;
|
|
1998
|
+
})(),
|
|
1999
|
+
rename: () => {
|
|
2000
|
+
/* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
|
|
2001
|
+
if (group.workspaceId !== void 0) onRenameRequest(group.workspaceId, group.label);
|
|
2002
|
+
},
|
|
2003
|
+
archive: () => {
|
|
2004
|
+
/* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
|
|
2005
|
+
if (group.workspaceId !== void 0) onArchiveRequest(group.workspaceId, group.label);
|
|
2006
|
+
},
|
|
2007
|
+
delete: () => {
|
|
2008
|
+
/* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
|
|
2009
|
+
if (group.workspaceId !== void 0) onDeleteRequest(group.workspaceId, group.label);
|
|
2010
|
+
}
|
|
1943
2011
|
}
|
|
1944
|
-
}
|
|
1945
2012
|
}),
|
|
1946
2013
|
(expandedSessionGroups.includes(group.key) ? group.sessions : group.sessions.slice(0, COLLAPSED_SESSION_LIMIT)).map((node) => {
|
|
1947
2014
|
const sameGroupDrag = drag !== null && drag.accountKey === group.key;
|
|
@@ -2148,13 +2215,14 @@ window.__ModuleLoader__.load({
|
|
|
2148
2215
|
function SearchResults({ useSessions, useSessionPendingInteraction, open, workspaces, archivedSessionIds, showArchived, query, remote, resultLimit, t }) {
|
|
2149
2216
|
const list = useSessions((s) => s);
|
|
2150
2217
|
const pendingInteractions = useSessionPendingInteraction((s) => s);
|
|
2218
|
+
const ungroupedLabel = t("group.ungrouped");
|
|
2151
2219
|
const currentRemote = remote.query === query ? remote : {
|
|
2152
2220
|
query,
|
|
2153
2221
|
status: "loading",
|
|
2154
2222
|
items: [],
|
|
2155
2223
|
hasMore: false
|
|
2156
2224
|
};
|
|
2157
|
-
const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, currentRemote, resultLimit, showArchived), [
|
|
2225
|
+
const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, currentRemote, resultLimit, showArchived, ungroupedLabel), [
|
|
2158
2226
|
list,
|
|
2159
2227
|
workspaces,
|
|
2160
2228
|
query,
|
|
@@ -2162,7 +2230,8 @@ window.__ModuleLoader__.load({
|
|
|
2162
2230
|
pendingInteractions,
|
|
2163
2231
|
currentRemote,
|
|
2164
2232
|
resultLimit,
|
|
2165
|
-
showArchived
|
|
2233
|
+
showArchived,
|
|
2234
|
+
ungroupedLabel
|
|
2166
2235
|
]);
|
|
2167
2236
|
const pending = currentRemote.status === "loading";
|
|
2168
2237
|
const failed = currentRemote.status === "error";
|
|
@@ -2209,7 +2278,7 @@ window.__ModuleLoader__.load({
|
|
|
2209
2278
|
* @param props - composed slot props (shell owner share + store + injected actions).
|
|
2210
2279
|
* @returns the region element tree.
|
|
2211
2280
|
*/
|
|
2212
|
-
function WorkspaceBrowser({ wide, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, unarchiveSession, deleteSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
|
|
2281
|
+
function WorkspaceBrowser({ wide, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, archiveWorkspaceSessions, unarchiveSession, deleteSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
|
|
2213
2282
|
const useEffectiveSessionPendingInteraction = useSessionPendingInteraction ?? useEmptySessionPendingInteraction;
|
|
2214
2283
|
const workspaces = useWorkspaces((state) => state.items);
|
|
2215
2284
|
const workspacePhase = useWorkspaces((state) => state.phase);
|
|
@@ -2404,6 +2473,34 @@ window.__ModuleLoader__.load({
|
|
|
2404
2473
|
showArchivedToast(formatArchiveError(reason, t));
|
|
2405
2474
|
});
|
|
2406
2475
|
};
|
|
2476
|
+
const [archiveWorkspaceTarget, setArchiveWorkspaceTarget] = (0, react.useState)(null);
|
|
2477
|
+
const [archivingWorkspace, setArchivingWorkspace] = (0, react.useState)(false);
|
|
2478
|
+
const [archiveWorkspaceError, setArchiveWorkspaceError] = (0, react.useState)(null);
|
|
2479
|
+
const closeArchiveWorkspace = () => {
|
|
2480
|
+
if (archivingWorkspace) return;
|
|
2481
|
+
setArchiveWorkspaceTarget(null);
|
|
2482
|
+
setArchiveWorkspaceError(null);
|
|
2483
|
+
};
|
|
2484
|
+
const onArchiveWorkspaceRequest = (workspaceId, title) => {
|
|
2485
|
+
const target = archiveWorkspaceDialogTarget(workspaces, workspaceId, title, archivedSet);
|
|
2486
|
+
if (target === null) return;
|
|
2487
|
+
setArchiveWorkspaceTarget(target);
|
|
2488
|
+
setArchiveWorkspaceError(null);
|
|
2489
|
+
};
|
|
2490
|
+
const confirmArchiveWorkspace = () => {
|
|
2491
|
+
/* v8 ignore next -- the Modal is absent without a target and its button is disabled while archiving. */
|
|
2492
|
+
if (archivingWorkspace || archiveWorkspaceTarget === null) return;
|
|
2493
|
+
setArchivingWorkspace(true);
|
|
2494
|
+
setArchiveWorkspaceError(null);
|
|
2495
|
+
archiveWorkspaceSessions(archiveWorkspaceTarget.workspaceId).then(() => {
|
|
2496
|
+
setArchivingWorkspace(false);
|
|
2497
|
+
setArchiveWorkspaceTarget(null);
|
|
2498
|
+
}).catch((reason) => {
|
|
2499
|
+
const state = archiveWorkspaceDialogFailureState(archiveWorkspaceTarget, reason, t);
|
|
2500
|
+
setArchivingWorkspace(state.archiving);
|
|
2501
|
+
setArchiveWorkspaceError(state.error);
|
|
2502
|
+
});
|
|
2503
|
+
};
|
|
2407
2504
|
const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
|
|
2408
2505
|
const [deleting, setDeleting] = (0, react.useState)(false);
|
|
2409
2506
|
const [deleteCommittedId, setDeleteCommittedId] = (0, react.useState)(null);
|
|
@@ -2668,6 +2765,7 @@ window.__ModuleLoader__.load({
|
|
|
2668
2765
|
setRenameDraft(currentTitle);
|
|
2669
2766
|
setRenameError(null);
|
|
2670
2767
|
},
|
|
2768
|
+
onArchiveRequest: onArchiveWorkspaceRequest,
|
|
2671
2769
|
onDeleteRequest: (workspaceId, title) => {
|
|
2672
2770
|
setDeleteTarget({
|
|
2673
2771
|
workspaceId,
|
|
@@ -2807,6 +2905,34 @@ window.__ModuleLoader__.load({
|
|
|
2807
2905
|
children: deleteError
|
|
2808
2906
|
})]
|
|
2809
2907
|
}),
|
|
2908
|
+
(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2909
|
+
open: archiveWorkspaceTarget !== null,
|
|
2910
|
+
onClose: closeArchiveWorkspace,
|
|
2911
|
+
closeLabel: t("close"),
|
|
2912
|
+
title: archiveWorkspaceTarget === null ? "" : t("archiveWorkspace.title", { n: archiveWorkspaceTarget.count }),
|
|
2913
|
+
...archiveWorkspaceTarget === null ? {} : { description: t("archiveWorkspace.desc", { name: archiveWorkspaceTarget.title }) },
|
|
2914
|
+
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2915
|
+
variant: "outline",
|
|
2916
|
+
disabled: archivingWorkspace,
|
|
2917
|
+
onClick: closeArchiveWorkspace,
|
|
2918
|
+
children: t("cancel")
|
|
2919
|
+
}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2920
|
+
variant: "outline",
|
|
2921
|
+
className: "dsham_archiveWorkspaceConfirm",
|
|
2922
|
+
disabled: archivingWorkspace,
|
|
2923
|
+
onClick: confirmArchiveWorkspace,
|
|
2924
|
+
children: t("archiveWorkspace.confirm")
|
|
2925
|
+
})] }),
|
|
2926
|
+
children: [archivingWorkspace && (0, react_jsx_runtime.jsx)("div", {
|
|
2927
|
+
className: WorkspaceBrowser_module_css_default.deleteStatus,
|
|
2928
|
+
role: "status",
|
|
2929
|
+
children: t("archiveWorkspace.pending")
|
|
2930
|
+
}), archiveWorkspaceError !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2931
|
+
className: WorkspaceBrowser_module_css_default.renameError,
|
|
2932
|
+
role: "alert",
|
|
2933
|
+
children: archiveWorkspaceError
|
|
2934
|
+
})]
|
|
2935
|
+
}),
|
|
2810
2936
|
(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2811
2937
|
open: deleteSessionTarget !== null,
|
|
2812
2938
|
onClose: closeDeleteSession,
|
|
@@ -2849,13 +2975,16 @@ window.__ModuleLoader__.load({
|
|
|
2849
2975
|
//#region dsh-archive-manager: settings section
|
|
2850
2976
|
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}}";
|
|
2851
2977
|
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}}";
|
|
2978
|
+
const ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS = ".dsham_settingsTitleRow{display:flex;align-items:center;gap:8px;min-width:0}.dsham_settingsLinks{display:flex;align-items:center;gap:4px}.dsham_settingsExternalLink{display:inline-flex;align-items:center;gap:5px;min-height:28px;padding:0 8px;color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dsham_settingsExternalLink:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsExternalLink:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_settingsExternalLink svg{flex:none}@media(max-width:720px){.dsham_settingsTitleRow{flex-wrap:wrap}}";
|
|
2979
|
+
const ARCHIVE_SETTINGS_DELETE_CONFIRM_CSS = ".dsham_settingsDeleteConfirm{color:var(--dsw-alias-state-error-primary)!important;background:transparent!important;border-color:var(--dsw-alias-state-error-primary)!important}.dsham_settingsDeleteConfirm:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)!important}.dsham_settingsDeleteConfirm:focus-visible{outline:2px solid var(--dsw-alias-state-error-secondary);outline-offset:2px}.dsham_settingsDeleteConfirm:disabled{cursor:not-allowed;opacity:.5}";
|
|
2980
|
+
const ARCHIVE_WORKSPACE_CONFIRM_CSS = ".dsham_archiveWorkspaceConfirm{color:var(--dsw-alias-state-error-primary)!important;background:transparent!important;border-color:var(--dsw-alias-state-error-primary)!important}.dsham_archiveWorkspaceConfirm:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)!important}.dsham_archiveWorkspaceConfirm:focus-visible{outline:2px solid var(--dsw-alias-state-error-secondary);outline-offset:2px}.dsham_archiveWorkspaceConfirm:disabled{cursor:not-allowed;opacity:.5}";
|
|
2852
2981
|
const ARCHIVE_SETTINGS_SELECTION_CSS = ".dsham_settingsSelection{display:flex;align-items:center;gap:10px;min-height:40px;margin:-8px 0 16px;padding:4px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-button-elevated-fill))}.dsham_settingsSelectionToggle{display:inline-flex;align-items:center;gap:8px;min-width:0;color:var(--dsw-alias-label-primary);font-size:13px;cursor:pointer}.dsham_settingsCheckbox{width:16px;height:16px;margin:0;accent-color:#3b82f6}.dsham_settingsSelectionCount{margin-right:auto;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsSelectionAction{min-height:28px;padding:0 10px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;cursor:pointer;font:inherit;font-size:12px;font-weight:500}.dsham_settingsSelectionAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsSelectionDelete{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.dsham_settingsSelectionAction:disabled{cursor:not-allowed;opacity:.5}@media(max-width:720px){.dsham_settingsSelection{flex-wrap:wrap}.dsham_settingsSelectionCount{margin-right:0}.dsham_settingsSelectionAction{margin-left:auto}.dsham_settingsSelectionAction+.dsham_settingsSelectionAction{margin-left:-2px}}";
|
|
2853
2982
|
const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:0 auto!important}@media(max-width:720px){.dsham_settings{margin:0 auto!important}}";
|
|
2854
2983
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
|
|
2855
2984
|
const tag = document.createElement("style");
|
|
2856
2985
|
tag.dataset.plugin = "@michengai/dsh-archive-manager";
|
|
2857
2986
|
tag.dataset.pluginCss = "dsh-archive-manager/ArchiveSettings.layout.css";
|
|
2858
|
-
tag.textContent = ARCHIVE_SETTINGS_LAYOUT_OVERRIDE;
|
|
2987
|
+
tag.textContent = ARCHIVE_SETTINGS_LAYOUT_OVERRIDE + ARCHIVE_SETTINGS_DELETE_CONFIRM_CSS + ARCHIVE_WORKSPACE_CONFIRM_CSS;
|
|
2859
2988
|
document.head.appendChild(tag);
|
|
2860
2989
|
}
|
|
2861
2990
|
/** 设置页筛选/排序:自定义菜单,避免原生 select 弹出系统浅色下拉。 */
|
|
@@ -3291,7 +3420,7 @@ window.__ModuleLoader__.load({
|
|
|
3291
3420
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
3292
3421
|
className: "dsham_settings",
|
|
3293
3422
|
"aria-label": t("archives.title"),
|
|
3294
|
-
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) => {
|
|
3423
|
+
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) => {
|
|
3295
3424
|
const target = archivedBatchTargetForGroup(group.key);
|
|
3296
3425
|
const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
|
|
3297
3426
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -3313,7 +3442,7 @@ window.__ModuleLoader__.load({
|
|
|
3313
3442
|
closeLabel: t("close"),
|
|
3314
3443
|
title: deleteDialogTitle,
|
|
3315
3444
|
...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
|
|
3316
|
-
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 })] }),
|
|
3445
|
+
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 })] }),
|
|
3317
3446
|
children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
|
|
3318
3447
|
})]
|
|
3319
3448
|
});
|
|
@@ -3358,6 +3487,8 @@ window.__ModuleLoader__.load({
|
|
|
3358
3487
|
"archived.notOpenable": "已归档,取消归档后可继续对话",
|
|
3359
3488
|
"archives.title": "已归档的聊天",
|
|
3360
3489
|
"archives.description": "管理已归档的会话。",
|
|
3490
|
+
"archives.viewProject": "GitHub",
|
|
3491
|
+
"archives.feedback": "问题反馈",
|
|
3361
3492
|
"archives.empty": "暂无已归档会话",
|
|
3362
3493
|
"archives.emptyFiltered": "没有匹配的已归档聊天",
|
|
3363
3494
|
"archives.searchPlaceholder": "搜索已归档聊天",
|
|
@@ -3425,6 +3556,11 @@ window.__ModuleLoader__.load({
|
|
|
3425
3556
|
"delete.pending": "正在删除工作区…",
|
|
3426
3557
|
"menu.fork": "分叉会话",
|
|
3427
3558
|
"menu.archiveSession": "归档会话",
|
|
3559
|
+
"menu.archiveWorkspace": "归档全部聊天",
|
|
3560
|
+
"archiveWorkspace.title": "归档 {n} 个聊天?",
|
|
3561
|
+
"archiveWorkspace.desc": "这会将“{name}”中的聊天归档。之后你可以在已归档的聊天中找到它们。",
|
|
3562
|
+
"archiveWorkspace.confirm": "全部归档",
|
|
3563
|
+
"archiveWorkspace.pending": "正在归档聊天…",
|
|
3428
3564
|
"sessions.count.one": "{n} 个会话",
|
|
3429
3565
|
"sessions.count.other": "{n} 个会话",
|
|
3430
3566
|
"actions.workspace.aria": "工作区“{name}”的操作",
|
|
@@ -3482,6 +3618,8 @@ window.__ModuleLoader__.load({
|
|
|
3482
3618
|
"archived.notOpenable": "This session is archived. Unarchive it to continue the conversation.",
|
|
3483
3619
|
"archives.title": "Archived chats",
|
|
3484
3620
|
"archives.description": "Manage archived sessions.",
|
|
3621
|
+
"archives.viewProject": "GitHub",
|
|
3622
|
+
"archives.feedback": "Issues",
|
|
3485
3623
|
"archives.empty": "No archived sessions.",
|
|
3486
3624
|
"archives.emptyFiltered": "No archived chats match your filters.",
|
|
3487
3625
|
"archives.searchPlaceholder": "Search archived chats",
|
|
@@ -3549,6 +3687,11 @@ window.__ModuleLoader__.load({
|
|
|
3549
3687
|
"delete.pending": "Deleting workspace…",
|
|
3550
3688
|
"menu.fork": "Fork session",
|
|
3551
3689
|
"menu.archiveSession": "Archive session",
|
|
3690
|
+
"menu.archiveWorkspace": "Archive all chats",
|
|
3691
|
+
"archiveWorkspace.title": "Archive {n} chats?",
|
|
3692
|
+
"archiveWorkspace.desc": "This archives the chats in “{name}”. You can find them later in Archived chats.",
|
|
3693
|
+
"archiveWorkspace.confirm": "Archive all",
|
|
3694
|
+
"archiveWorkspace.pending": "Archiving chats…",
|
|
3552
3695
|
"sessions.count.one": "{n} session",
|
|
3553
3696
|
"sessions.count.other": "{n} sessions",
|
|
3554
3697
|
"actions.workspace.aria": "Workspace actions for {name}",
|
|
@@ -3754,6 +3897,12 @@ window.__ModuleLoader__.load({
|
|
|
3754
3897
|
* effect-scoped inside `applyWorkspaceBrowser`).
|
|
3755
3898
|
* @param ctx - client root context.
|
|
3756
3899
|
*/
|
|
3900
|
+
async function archiveWorkspaceSessionsAndRefresh(registry, workspaceId, refresh) {
|
|
3901
|
+
const result = await registry.archiveWorkspaceSessions(workspaceId);
|
|
3902
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
3903
|
+
await refresh();
|
|
3904
|
+
return result.value;
|
|
3905
|
+
}
|
|
3757
3906
|
async function apply(ctx) {
|
|
3758
3907
|
const remote = ctx.get("remote");
|
|
3759
3908
|
let disposeRemote = () => {};
|
|
@@ -3806,6 +3955,11 @@ window.__ModuleLoader__.load({
|
|
|
3806
3955
|
if (!result.ok) throw new Error(result.error.message);
|
|
3807
3956
|
await refreshSessionList();
|
|
3808
3957
|
};
|
|
3958
|
+
const archiveWorkspaceSessions = async (workspaceId) => {
|
|
3959
|
+
const registry = ctx.get("remote.workspaceRegistry");
|
|
3960
|
+
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
3961
|
+
return archiveWorkspaceSessionsAndRefresh(registry, workspaceId, refreshSessionList);
|
|
3962
|
+
};
|
|
3809
3963
|
const deleteSession = async (sessionId) => {
|
|
3810
3964
|
const registry = ctx.get("remote.workspaceRegistry");
|
|
3811
3965
|
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
@@ -3876,6 +4030,7 @@ window.__ModuleLoader__.load({
|
|
|
3876
4030
|
archiveSession: async (sessionId) => {
|
|
3877
4031
|
await ctx.workspaces.archiveSession(sessionId);
|
|
3878
4032
|
},
|
|
4033
|
+
archiveWorkspaceSessions,
|
|
3879
4034
|
unarchiveSession,
|
|
3880
4035
|
deleteSession,
|
|
3881
4036
|
insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
|
|
@@ -3943,6 +4098,10 @@ window.__ModuleLoader__.load({
|
|
|
3943
4098
|
pruneArchivedSelection,
|
|
3944
4099
|
toggleArchivedSelection,
|
|
3945
4100
|
archivedDeleteFeedback,
|
|
4101
|
+
archiveableWorkspaceSessionCount,
|
|
4102
|
+
archiveWorkspaceDialogTarget,
|
|
4103
|
+
archiveWorkspaceDialogFailureState,
|
|
4104
|
+
archiveWorkspaceSessionsAndRefresh,
|
|
3946
4105
|
createWorkspaceViewStore,
|
|
3947
4106
|
bindObservable,
|
|
3948
4107
|
hasSplitClientStore,
|
package/lib/projcache.js
CHANGED
|
@@ -10,32 +10,54 @@ import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
|
10
10
|
import { trackTombstone } from "./tombstone.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* a fixed-length SHA-256 token and the original id is carried in the value.
|
|
17
|
-
*
|
|
18
|
-
* On rc.2 the runtime has no per-record layout, so the same domain remains a
|
|
19
|
-
* single JSON file. After that user upgrades to alpha.2, every already-safe
|
|
20
|
-
* hashed key can be migrated by the official backend without hitting the
|
|
21
|
-
* legacy colon bug.
|
|
13
|
+
* 当前投影缓存写入独立的 v2 域,避免 DSH alpha.2 将包含冒号的逻辑会话 ID
|
|
14
|
+
* 直接映射为 Windows 文件名。旧名 v1 与旧名 v2 域只在启动时读取并补缺迁入,
|
|
15
|
+
* 迁移不删除原始缓存,任意中断都可在后续启动继续。
|
|
22
16
|
*/
|
|
23
17
|
const storedCheckpointRecord = checkpointRecord.extend({
|
|
24
18
|
sessionId: checkpointIdentity.shape.cwd.unwrap()
|
|
25
19
|
});
|
|
26
20
|
const safeDomainInput = {
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
// 旧版 DSH 不支持同名域的版本迁移。v2 改用新域名,确保升级时不会在
|
|
22
|
+
// 打开目标域之前因旧 v1 元数据而终止启动。
|
|
23
|
+
name: "session_projcache_archive_manager_v2",
|
|
24
|
+
version: 2,
|
|
29
25
|
tables: { sessions: domainTable(storedCheckpointRecord) },
|
|
30
26
|
...(projectionCacheDomainSpec.layout === "per-record" ? { layout: "per-record" } : {})
|
|
31
27
|
};
|
|
32
28
|
const safeProjectionCacheDomainSpec = defineDomain(safeDomainInput);
|
|
33
29
|
|
|
34
30
|
/** The last official whole-file cache format used by DSH rc.2. */
|
|
31
|
+
const legacyOptionalIdentityFields = {};
|
|
32
|
+
for (const field of ["isSeeded", "inheritedEventCount"]) {
|
|
33
|
+
// rc.2 的身份 schema 尚未定义这些字段;向 Zod 传入不存在的字段会在
|
|
34
|
+
// 首次解析历史记录时抛错,因此只能按当前运行时实际的 schema 构造掩码。
|
|
35
|
+
if (Object.hasOwn(checkpointIdentity.shape, field)) legacyOptionalIdentityFields[field] = true;
|
|
36
|
+
}
|
|
37
|
+
const legacyCheckpointRecord = checkpointRecord.extend({
|
|
38
|
+
identity: checkpointIdentity.partial(legacyOptionalIdentityFields)
|
|
39
|
+
});
|
|
40
|
+
const legacyStoredCheckpointRecord = legacyCheckpointRecord.extend({
|
|
41
|
+
sessionId: checkpointIdentity.shape.cwd.unwrap()
|
|
42
|
+
});
|
|
43
|
+
/** v0.1.24 短暂使用过的旧名 v2 安全缓存域。 */
|
|
44
|
+
const legacySafeV2ProjectionCacheDomainSpec = defineDomain({
|
|
45
|
+
name: "session_projcache_archive_manager",
|
|
46
|
+
version: 2,
|
|
47
|
+
tables: { sessions: domainTable(storedCheckpointRecord) },
|
|
48
|
+
...(projectionCacheDomainSpec.layout === "per-record" ? { layout: "per-record" } : {})
|
|
49
|
+
});
|
|
50
|
+
/** v0.1.23 及以前归档管理器自身使用的安全缓存域。 */
|
|
51
|
+
const legacySafeProjectionCacheDomainSpec = defineDomain({
|
|
52
|
+
name: "session_projcache_archive_manager",
|
|
53
|
+
version: 1,
|
|
54
|
+
tables: { sessions: domainTable(legacyStoredCheckpointRecord) },
|
|
55
|
+
...(projectionCacheDomainSpec.layout === "per-record" ? { layout: "per-record" } : {})
|
|
56
|
+
});
|
|
35
57
|
const legacyProjectionCacheDomainSpec = defineDomain({
|
|
36
58
|
name: "session_projcache",
|
|
37
59
|
version: 3,
|
|
38
|
-
tables: { sessions: domainTable(
|
|
60
|
+
tables: { sessions: domainTable(legacyCheckpointRecord) }
|
|
39
61
|
});
|
|
40
62
|
|
|
41
63
|
function projectionCacheStorageKey(sessionId) {
|
|
@@ -107,12 +129,59 @@ async function readSourceRecords(ctx, spec, label) {
|
|
|
107
129
|
}
|
|
108
130
|
}
|
|
109
131
|
|
|
132
|
+
async function readLegacySafeRecords(ctx, spec, label) {
|
|
133
|
+
const source = await readSourceRecords(ctx, spec, label);
|
|
134
|
+
const records = [];
|
|
135
|
+
for (const [physicalKey, stored] of source.records) {
|
|
136
|
+
if (stored === null || typeof stored !== "object" || typeof stored.sessionId !== "string") {
|
|
137
|
+
ctx.logger.warn(`archive-manager projcache: ${label} row "${physicalKey}" import skipped because its session id is invalid`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (projectionCacheStorageKey(stored.sessionId) !== physicalKey) {
|
|
141
|
+
ctx.logger.warn(`archive-manager projcache: ${label} row "${physicalKey}" import skipped because its storage key does not match the session id`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
records.push([stored.sessionId, unwrapStoredCheckpoint(stored)]);
|
|
145
|
+
}
|
|
146
|
+
return { ...source, records };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 旧版记录没有完整身份时,只能安全补齐未播种会话的零继承切点。
|
|
151
|
+
* 播种会话无法从头部反推出精确切点,宁可跳过并按需重建。
|
|
152
|
+
*/
|
|
153
|
+
function normalizeLegacyRecord(record) {
|
|
154
|
+
if (record === null || typeof record !== "object" || record.identity === null || typeof record.identity !== "object") return void 0;
|
|
155
|
+
const identity = record.identity;
|
|
156
|
+
const inheritedEventCount = identity.inheritedEventCount;
|
|
157
|
+
const validOffset = Number.isSafeInteger(inheritedEventCount) && inheritedEventCount >= 0;
|
|
158
|
+
if (identity.isSeeded === true) {
|
|
159
|
+
if (!validOffset) return void 0;
|
|
160
|
+
return record;
|
|
161
|
+
}
|
|
162
|
+
if (identity.isSeeded !== void 0 && identity.isSeeded !== false) return void 0;
|
|
163
|
+
if (inheritedEventCount !== void 0 && inheritedEventCount !== 0) return void 0;
|
|
164
|
+
return {
|
|
165
|
+
...record,
|
|
166
|
+
identity: {
|
|
167
|
+
...identity,
|
|
168
|
+
isSeeded: false,
|
|
169
|
+
inheritedEventCount: 0
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
110
174
|
async function importMissingRecords(ctx, target, records, label) {
|
|
111
175
|
let imported = 0;
|
|
112
176
|
for (const [sessionId, record] of records) {
|
|
113
177
|
if (target.has(sessionId)) continue;
|
|
114
178
|
try {
|
|
115
|
-
|
|
179
|
+
const normalized = normalizeLegacyRecord(record);
|
|
180
|
+
if (normalized === void 0) {
|
|
181
|
+
ctx.logger.warn(`archive-manager projcache: ${label} row "${sessionId}" import skipped because its lifecycle identity is incomplete`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
await target.put(sessionId, normalized);
|
|
116
185
|
imported += 1;
|
|
117
186
|
} catch (error) {
|
|
118
187
|
ctx.logger.warn(`archive-manager projcache: ${label} row "${sessionId}" import failed: ${String(error)}`);
|
|
@@ -122,30 +191,33 @@ async function importMissingRecords(ctx, target, records, label) {
|
|
|
122
191
|
}
|
|
123
192
|
|
|
124
193
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* fall through to the current source; source-open failures stay fail-soft.
|
|
128
|
-
* Imports are add-only: a newer compatibility-domain checkpoint always wins,
|
|
129
|
-
* and a partially completed import resumes on the next startup.
|
|
194
|
+
* 合并归档管理器旧名 v2、旧名 v1、官方新版逐会话与旧版整文件缓存。目标域记录
|
|
195
|
+
* 最高优先,随后按新旧顺序只补缺失记录;任一来源失败时仍继续尝试另一个来源。
|
|
130
196
|
*/
|
|
131
197
|
async function importPreviousProjectionCache(ctx, target) {
|
|
198
|
+
const legacySafeV2 = await readLegacySafeRecords(ctx, legacySafeV2ProjectionCacheDomainSpec, "archive-manager v2");
|
|
199
|
+
const legacySafe = await readLegacySafeRecords(ctx, legacySafeProjectionCacheDomainSpec, "archive-manager v1");
|
|
132
200
|
const legacy = await readSourceRecords(ctx, legacyProjectionCacheDomainSpec, "legacy v3");
|
|
133
|
-
if (!legacy.opened) return 0;
|
|
134
|
-
if (legacy.records.length > 0) return importMissingRecords(ctx, target, legacy.records, "legacy v3");
|
|
135
201
|
const sameSpec = projectionCacheDomainSpec.version === legacyProjectionCacheDomainSpec.version
|
|
136
202
|
&& projectionCacheDomainSpec.layout === legacyProjectionCacheDomainSpec.layout;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
|
|
203
|
+
let imported = 0;
|
|
204
|
+
if (legacySafeV2.opened) imported += await importMissingRecords(ctx, target, legacySafeV2.records, "archive-manager v2");
|
|
205
|
+
if (legacySafe.opened) imported += await importMissingRecords(ctx, target, legacySafe.records, "archive-manager v1");
|
|
206
|
+
if (!sameSpec) {
|
|
207
|
+
const current = await readSourceRecords(ctx, projectionCacheDomainSpec, "current");
|
|
208
|
+
if (current.opened) imported += await importMissingRecords(ctx, target, current.records, "current");
|
|
209
|
+
}
|
|
210
|
+
if (legacy.opened) imported += await importMissingRecords(ctx, target, legacy.records, "legacy v3");
|
|
211
|
+
return imported;
|
|
141
212
|
}
|
|
142
213
|
//#region lib/types/index.js
|
|
143
214
|
/**
|
|
144
215
|
* dsh-archive-manager projcache 半边。
|
|
145
216
|
*
|
|
146
217
|
* `ArchiveProjectionCache` 继承上游 `SessionProjectionCache`,保留服务名
|
|
147
|
-
* `sessionProjectionCache` 与 fail-soft
|
|
148
|
-
* `
|
|
218
|
+
* `sessionProjectionCache` 与 fail-soft 写路径,但写入独立、路径安全的
|
|
219
|
+
* `session_projcache_archive_manager_v2` 域。旧名 v1 与 v2 域只在启动时
|
|
220
|
+
* 读取并补缺迁入;归档管理的
|
|
149
221
|
* `deleteSession` 增加三处守护:
|
|
150
222
|
*
|
|
151
223
|
* - `delete(id)` - 永久移除兼容域中该会话经 SHA-256 映射的缓存投影行,
|
|
@@ -255,6 +327,8 @@ export {
|
|
|
255
327
|
SafeSessionTable,
|
|
256
328
|
importPreviousProjectionCache,
|
|
257
329
|
legacyProjectionCacheDomainSpec,
|
|
330
|
+
legacySafeProjectionCacheDomainSpec,
|
|
331
|
+
legacySafeV2ProjectionCacheDomainSpec,
|
|
258
332
|
projectionCacheStorageKey,
|
|
259
333
|
safeProjectionCacheDomainSpec
|
|
260
334
|
};
|
package/lib/workspace.js
CHANGED
|
@@ -65,6 +65,12 @@ const sessionIdSchema = {
|
|
|
65
65
|
return value;
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
|
+
const workspaceIdSchema = {
|
|
69
|
+
parse(value) {
|
|
70
|
+
if (typeof value !== "string" || value.length === 0) throw new TypeError(`workspaceId must be a non-empty string, got ${String(value)}`);
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
68
74
|
const archivedSetSchema = {
|
|
69
75
|
parse(value) {
|
|
70
76
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
@@ -96,6 +102,14 @@ const unarchivedBatchSchema = {
|
|
|
96
102
|
return value;
|
|
97
103
|
}
|
|
98
104
|
};
|
|
105
|
+
const archivedWorkspaceBatchSchema = {
|
|
106
|
+
parse(value) {
|
|
107
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
108
|
+
if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIds must be a string array");
|
|
109
|
+
if (!Array.isArray(value.archivedSessionIdsAdded) || value.archivedSessionIdsAdded.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIdsAdded must be a string array");
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
99
113
|
const deletedBatchSchema = {
|
|
100
114
|
parse(value) {
|
|
101
115
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
|
|
@@ -176,6 +190,25 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
|
|
|
176
190
|
},
|
|
177
191
|
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
178
192
|
},
|
|
193
|
+
{
|
|
194
|
+
id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveWorkspaceSessions",
|
|
195
|
+
service: "workspaceRegistry",
|
|
196
|
+
namespace: "workspaceRegistry",
|
|
197
|
+
method: "archiveWorkspaceSessions",
|
|
198
|
+
invocation: { kind: "direct" },
|
|
199
|
+
parameters: [{
|
|
200
|
+
name: "workspaceId",
|
|
201
|
+
wire: "workspaceId",
|
|
202
|
+
source: "json",
|
|
203
|
+
codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-workspace/types#WorkspaceId", schema: workspaceIdSchema }
|
|
204
|
+
}],
|
|
205
|
+
result: {
|
|
206
|
+
mode: "strict",
|
|
207
|
+
typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedWorkspaceBatch",
|
|
208
|
+
schema: archivedWorkspaceBatchSchema
|
|
209
|
+
},
|
|
210
|
+
sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
|
|
211
|
+
},
|
|
179
212
|
{
|
|
180
213
|
id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteArchivedSessions",
|
|
181
214
|
service: "workspaceRegistry",
|
|
@@ -248,6 +281,7 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
248
281
|
markRemoteMethod(this, "unarchiveSession");
|
|
249
282
|
markRemoteMethod(this, "deleteSession");
|
|
250
283
|
markRemoteMethod(this, "unarchiveSessions");
|
|
284
|
+
markRemoteMethod(this, "archiveWorkspaceSessions");
|
|
251
285
|
markRemoteMethod(this, "deleteArchivedSessions");
|
|
252
286
|
markRemoteMethod(this, "archivedSessionMetadata");
|
|
253
287
|
registerHostRemote(this.ctx);
|
|
@@ -279,14 +313,27 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
279
313
|
if (cache === void 0 || typeof cache.cachedSnapshot !== "function" || typeof cache.put !== "function") return false;
|
|
280
314
|
if (persistence === void 0 || typeof persistence.readFrom !== "function" || projections === void 0 || typeof projections.restore !== "function") return false;
|
|
281
315
|
try {
|
|
282
|
-
|
|
316
|
+
// 未播种会话的继承事件数恒为零,先查缓存可避免读取完整会话原文。
|
|
317
|
+
if (!header.isSeeded && cache.cachedSnapshot(header, 0) !== void 0) return false;
|
|
283
318
|
const stored = await persistence.readFrom(header.id, 0);
|
|
284
319
|
const meta = stored.meta ?? header;
|
|
285
|
-
|
|
320
|
+
if (meta.isSeeded === true && stored.inheritedEventCount === void 0) {
|
|
321
|
+
this.ctx.logger.warn(`archive-manager: projection repair for seeded archived session "${header.id}" skipped because its inherited event count is unavailable`);
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
const inheritedEventCount = stored.inheritedEventCount ?? 0;
|
|
325
|
+
if (meta.isSeeded !== true && inheritedEventCount !== 0) {
|
|
326
|
+
this.ctx.logger.warn(`archive-manager: projection repair for unseeded archived session "${header.id}" skipped because its inherited event count is not zero`);
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
if (cache.cachedSnapshot(meta, inheritedEventCount) !== void 0) return false;
|
|
330
|
+
const restored = projections.restore({}, stored.events, 0, meta, inheritedEventCount);
|
|
286
331
|
if (restored === void 0 || typeof restored !== "object" || restored.checkpoint === void 0) return false;
|
|
287
332
|
await cache.put(header.id, {
|
|
288
333
|
createdAt: meta.createdAt,
|
|
289
|
-
...meta.cwd === void 0 ? {} : { cwd: meta.cwd }
|
|
334
|
+
...(meta.cwd === void 0 ? {} : { cwd: meta.cwd }),
|
|
335
|
+
isSeeded: meta.isSeeded ?? false,
|
|
336
|
+
inheritedEventCount
|
|
290
337
|
}, restored.checkpoint);
|
|
291
338
|
return true;
|
|
292
339
|
} catch (error) {
|
|
@@ -315,6 +362,36 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
315
362
|
});
|
|
316
363
|
}
|
|
317
364
|
/**
|
|
365
|
+
* 将一个工作区内所有会话加入归档集合。先确认所有待归档会话仍存在,
|
|
366
|
+
* 再执行单次状态写入,因此未知会话不会导致项目只归档一部分。
|
|
367
|
+
*/
|
|
368
|
+
async archiveWorkspaceSessions(workspaceId) {
|
|
369
|
+
return this.enqueueOperation(async () => {
|
|
370
|
+
workspaceId = workspaceIdSchema.parse(workspaceId);
|
|
371
|
+
const workspace = this.requireTable().get(workspaceId);
|
|
372
|
+
if (workspace === void 0) throw new Error(`unknown workspace "${workspaceId}"`);
|
|
373
|
+
const state = this.requireState();
|
|
374
|
+
const archived = new Set(state.archivedSessionIds);
|
|
375
|
+
const archivedSessionIdsAdded = [...new Set(workspace.sessionIds)].filter((sessionId) => !archived.has(sessionId));
|
|
376
|
+
for (const sessionId of archivedSessionIdsAdded) {
|
|
377
|
+
if (!await this.sessionKnown(sessionId)) throw new ArchiveUnknownSessionError(sessionId);
|
|
378
|
+
}
|
|
379
|
+
if (archivedSessionIdsAdded.length === 0) return {
|
|
380
|
+
archivedSessionIds: [...state.archivedSessionIds],
|
|
381
|
+
archivedSessionIdsAdded
|
|
382
|
+
};
|
|
383
|
+
const next = {
|
|
384
|
+
...state,
|
|
385
|
+
archivedSessionIds: [...state.archivedSessionIds, ...archivedSessionIdsAdded]
|
|
386
|
+
};
|
|
387
|
+
await this.setState(next);
|
|
388
|
+
return {
|
|
389
|
+
archivedSessionIds: [...next.archivedSessionIds],
|
|
390
|
+
archivedSessionIdsAdded
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
318
395
|
* 按宿主权威归档集合一次恢复全部、一个工作区或未分组的归档会话。
|
|
319
396
|
* 目标全部来自已归档集合,因此即使日志已被外部移除,也会清掉陈旧归档标记。
|
|
320
397
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-archive-manager",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "NPM-installable DSH Web plugin for managing archived sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.21.0",
|
|
@@ -28,22 +28,24 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "node scripts/check-package.mjs",
|
|
30
30
|
"test": "node --test test/*.test.mjs",
|
|
31
|
+
"test:compat": "node scripts/test-legacy-compatibility.mjs",
|
|
31
32
|
"pack:check": "pnpm pack --dry-run",
|
|
32
|
-
"prepublishOnly": "pnpm build && pnpm test"
|
|
33
|
+
"prepublishOnly": "pnpm build && pnpm test && pnpm test:compat"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
36
37
|
"@deepseek-ai/dsh-client-connection": ">=0.1.0-rc.5 <0.2.0",
|
|
37
38
|
"@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.5 <0.2.0",
|
|
38
39
|
"@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.5 <0.2.0",
|
|
40
|
+
"@deepseek-ai/dsh-client-store": ">=0.1.0-rc.5 <0.2.0",
|
|
39
41
|
"@deepseek-ai/dsh-client-ui-conversation": ">=0.1.0-rc.5 <0.2.0",
|
|
40
42
|
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.5 <0.2.0",
|
|
41
43
|
"@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.0-rc.5 <0.2.0",
|
|
42
44
|
"@deepseek-ai/dsh-client-ui-workspace": ">=0.1.0-rc.5 <0.2.0",
|
|
43
45
|
"@deepseek-ai/dsh-session": ">=0.1.0-rc.5 <0.2.0",
|
|
44
46
|
"@deepseek-ai/dsh-session-projection-cache": ">=0.1.0-rc.5 <0.2.0",
|
|
45
|
-
"@deepseek-ai/dsh-storage-domain": ">=0.1.0-rc.5 <0.2.0",
|
|
46
47
|
"@deepseek-ai/dsh-spill-local": ">=0.1.0-rc.5 <0.2.0",
|
|
48
|
+
"@deepseek-ai/dsh-storage-domain": ">=0.1.0-rc.5 <0.2.0",
|
|
47
49
|
"@deepseek-ai/dsh-typert-protocol": ">=0.1.0-rc.5 <0.2.0",
|
|
48
50
|
"@deepseek-ai/dsh-typert-registry": ">=0.1.0-rc.5 <0.2.0",
|
|
49
51
|
"@deepseek-ai/dsh-workspace": ">=0.1.0-rc.5 <0.2.0"
|
|
@@ -51,20 +53,35 @@
|
|
|
51
53
|
"peerDependenciesMeta": {
|
|
52
54
|
"@deepseek-ai/dsh-client-runtime": {
|
|
53
55
|
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@deepseek-ai/dsh-client-store": {
|
|
58
|
+
"optional": true
|
|
54
59
|
}
|
|
55
60
|
},
|
|
56
61
|
"devDependencies": {
|
|
57
|
-
"@deepseek-ai/cordis": "4.0.
|
|
58
|
-
"@deepseek-ai/dsh-api-gateway": "0.1.
|
|
59
|
-
"@deepseek-ai/dsh-client-
|
|
60
|
-
"@deepseek-ai/dsh-client-
|
|
61
|
-
"@deepseek-ai/dsh-client-
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
62
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
63
|
+
"@deepseek-ai/dsh-api-gateway": "0.1.2-alpha.5",
|
|
64
|
+
"@deepseek-ai/dsh-client-connection": "0.1.2-alpha.5",
|
|
65
|
+
"@deepseek-ai/dsh-client-locale": "0.1.2-alpha.5",
|
|
66
|
+
"@deepseek-ai/dsh-client-store": "0.1.2-alpha.5",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.5",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.5",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.1.2-alpha.5",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.5",
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-workspace": "0.1.2-alpha.5",
|
|
72
|
+
"@deepseek-ai/dsh-invariants": "0.1.2-alpha.5",
|
|
73
|
+
"@deepseek-ai/dsh-scope": "0.1.2-alpha.5",
|
|
74
|
+
"@deepseek-ai/dsh-session": "0.1.2-alpha.5",
|
|
75
|
+
"@deepseek-ai/dsh-session-persistence": "0.1.2-alpha.5",
|
|
76
|
+
"@deepseek-ai/dsh-session-projection": "0.1.2-alpha.5",
|
|
77
|
+
"@deepseek-ai/dsh-session-projection-cache": "0.1.2-alpha.5",
|
|
78
|
+
"@deepseek-ai/dsh-spill": "0.1.2-alpha.5",
|
|
79
|
+
"@deepseek-ai/dsh-spill-local": "0.1.2-alpha.5",
|
|
80
|
+
"@deepseek-ai/dsh-storage": "0.1.2-alpha.5",
|
|
81
|
+
"@deepseek-ai/dsh-storage-domain": "0.1.2-alpha.5",
|
|
82
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.2-alpha.5",
|
|
83
|
+
"@deepseek-ai/dsh-typert-registry": "0.1.2-alpha.5",
|
|
84
|
+
"@deepseek-ai/dsh-workspace": "0.1.2-alpha.5",
|
|
68
85
|
"react": "18.3.1",
|
|
69
86
|
"react-dom": "18.3.1"
|
|
70
87
|
},
|