@michengai/dsh-archive-manager 0.1.41 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  Published release notes are retained below; new versions are added without removing earlier entries.
6
6
 
7
+ ## 0.1.42 - 2026-09-14
8
+
9
+ - Add Archived / Unarchived tabs with Archived selected by default. Select sessions across projects for batch archiving, or archive all chats in a project or ungrouped section from its menu.
10
+ - Unify session action labels and make batch archive buttons and menu actions easier to identify. Keep the confirmation dialog open with an error on archive failure for direct retries, and clear stale feedback.
11
+ - Add the Typert `workspaceRegistry/archiveSessions` endpoint accepting `{ sessionIds: string[] }` to archive sessions across projects, with deduplication, idempotency, and whole-batch rejection for invalid sessions.
12
+
7
13
  ## 0.1.41 - 2026-09-14
8
14
 
9
15
  - Improve the archived-session bulk action bar with grouped selection details and actions, fixing wrapped button labels in narrow content areas.
@@ -4,6 +4,12 @@
4
4
 
5
5
  以下发布说明会持续保留;新增版本时不再删除较早记录。
6
6
 
7
+ ## 0.1.42 - 2026-09-14
8
+
9
+ - 新增「已归档 / 未归档」页签,默认展示已归档;支持跨项目选择会话批量归档,也可通过项目或未分组菜单归档该组全部聊天。
10
+ - 统一会话操作文案,提升批量归档按钮与菜单的辨识度;归档失败时保留确认框并显示错误,可直接重试,同时清理过期提示。
11
+ - 新增 Typert `workspaceRegistry/archiveSessions` 接口,接受 `{ sessionIds: string[] }` 批量归档跨项目会话,支持去重与幂等,无效会话导致整批拒绝。
12
+
7
13
  ## 0.1.41 - 2026-09-14
8
14
 
9
15
  - 优化归档会话的批量操作栏:选择摘要与操作分组显示,修复窄内容区内按钮文字折行的问题。
package/README.md CHANGED
@@ -76,16 +76,22 @@ Restart DSH Web, then hard-refresh your browser with `Ctrl+Shift+R`. Open **Sett
76
76
  | Archive a workspace | Open the workspace menu and choose the option to archive its chats |
77
77
  | Find an archive | Open **Settings → Archived sessions**, then search titles or filter by project |
78
78
  | Change the order | Sort by update time, creation time, or title |
79
- | Restore one chat | Click **Unarchive** beside the session |
79
+ | Restore one chat | Click **Restore** beside the session |
80
+ | Archive a project or ungrouped chats | On **Unarchived**, open the group’s **…** menu and confirm archiving all chats in that group, regardless of search filters |
81
+ | Archive across projects | Switch to **Unarchived**, select sessions across projects, then click **Archive** and confirm |
80
82
  | Restore or delete in bulk | Select chats and use the bulk actions, or use the project menu or page-wide actions |
81
83
 
82
- Selections persist when filters change. Check the hidden selection count before applying bulk actions, or clear your selection first.
84
+ The page opens on **Archived**, with **Unarchived** on the right. Switching tabs clears selections; changing search or project filters preserves them. Check the hidden selection count before applying bulk actions, or clear your selection first.
85
+
86
+ **Restore all / Delete all** remain on the Archived tab and apply to archived sessions across all projects, regardless of filters. Unarchived excludes subagents and blank placeholders and supports sorting by update time or title.
87
+
88
+ Developer API: Typert `workspaceRegistry/archiveSessions` accepts `{ sessionIds: string[] }` and returns `{ archivedSessionIds, archivedSessionIdsAdded }`. Call `registry.archiveSessions(ids)` and check the Typert `ok` result; the method uses the existing host authentication and RPC channel. The host validates and deduplicates the batch before one state write. Already archived IDs are idempotent; unknown sessions return `UNKNOWN_SESSION` without a partial archive.
83
89
 
84
90
  ### View and continue archived conversations
85
91
 
86
92
  Available starting with `0.1.40`:
87
93
 
88
- - **View conversation**: open the native DSH session to view messages, attachments, and tool details. Continue chatting while keeping the session archived.
94
+ - **Open session**: open the native DSH session to view messages, attachments, and tool details. Continue chatting while keeping the session archived.
89
95
  - **Restore and open**: unarchive the session and open it to resume work.
90
96
 
91
97
  ## Updates
package/README.zh-CN.md CHANGED
@@ -76,16 +76,22 @@ dsh plugin --profile web add @michengai/dsh-archive-manager@latest --registry=ht
76
76
  | 归档整个工作区 | 打开工作区菜单,选择归档该工作区的会话 |
77
77
  | 查找归档 | 打开「设置 → 归档会话」,搜索标题或按项目筛选 |
78
78
  | 调整排列顺序 | 按更新时间、创建时间或标题排序 |
79
- | 恢复一条会话 | 点击会话右侧的「取消归档」 |
79
+ | 恢复一条会话 | 点击会话右侧的「恢复」 |
80
+ | 归档某个项目或未分组 | 在「未归档」页点击分组右侧“…”菜单,确认归档该组全部聊天;不受搜索筛选影响 |
81
+ | 跨项目批量归档 | 切换到「未归档」,跨项目勾选会话后点击「归档」并确认 |
80
82
  | 批量恢复或删除 | 勾选会话后使用批量操作;也可使用项目菜单或页面顶部的全部操作 |
81
83
 
82
- 切换筛选条件会保留已选会话。批量操作前留意隐藏的已选数量,或先清空选择。
84
+ 页面默认打开「已归档」,右侧「未归档」用于批量归档。切换 TAB 会清空选择;切换搜索或项目筛选会保留已选会话。批量操作前留意隐藏的已选数量,或先清空选择。
85
+
86
+ 「全部恢复 / 全部删除」仅在已归档页显示,作用于所有项目的归档会话,不受筛选影响。未归档页排除子代理和空白占位会话,支持按更新时间或标题排序。
87
+
88
+ 开发接口:Typert `workspaceRegistry/archiveSessions` 接收 `{ sessionIds: string[] }`,返回 `{ archivedSessionIds, archivedSessionIdsAdded }`。客户端调用 `registry.archiveSessions(ids)` 并检查 Typert 的 `ok` 结果;接口复用宿主连接的认证与调用通道。宿主先校验、去重,再一次写入;已归档项幂等跳过,未知会话返回 `UNKNOWN_SESSION` 且不部分归档。
83
89
 
84
90
  ### 查看并继续归档对话
85
91
 
86
92
  从 `0.1.40` 起支持以下操作:
87
93
 
88
- - **查看对话**:打开 DSH 原生会话页,查看消息、附件和工具详情;可直接继续聊天,保持归档状态。
94
+ - **打开会话**:打开 DSH 原生会话页,查看消息、附件和工具详情;可直接继续聊天,保持归档状态。
89
95
  - **恢复并打开**:取消归档后进入原会话,继续工作。
90
96
 
91
97
  ## 更新
package/lib/client.js CHANGED
@@ -505,6 +505,12 @@
505
505
  return value;
506
506
  }
507
507
  };
508
+ const archiveSessionIdsSchema = {
509
+ parse(value) {
510
+ if (!Array.isArray(value)) throw new TypeError("sessionIds must be an array");
511
+ return [...new Set(value.map((id) => sessionIdSchema.parse(id)))];
512
+ }
513
+ };
508
514
  const archivedWorkspaceBatchSchema = {
509
515
  parse(value) {
510
516
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
@@ -591,6 +597,25 @@
591
597
  },
592
598
  sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
593
599
  },
600
+ {
601
+ id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveSessions",
602
+ service: "workspaceRegistry",
603
+ namespace: "workspaceRegistry",
604
+ method: "archiveSessions",
605
+ invocation: { kind: "direct" },
606
+ parameters: [{
607
+ name: "sessionIds",
608
+ wire: "sessionIds",
609
+ source: "json",
610
+ codec: { mode: "strict", typeSymbol: "@michengai/dsh-archive-manager/types#ArchiveSessionIds", schema: archiveSessionIdsSchema }
611
+ }],
612
+ result: {
613
+ mode: "strict",
614
+ typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedWorkspaceBatch",
615
+ schema: archivedWorkspaceBatchSchema
616
+ },
617
+ sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
618
+ },
594
619
  {
595
620
  id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveWorkspaceSessions",
596
621
  service: "workspaceRegistry",
@@ -729,9 +754,27 @@
729
754
  function GithubMark16() {
730
755
  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" }) });
731
756
  }
732
- function ArchivedSessionsSection({ sessionStore, workspaceStore, unarchiveSession, deleteSession, unarchiveSessions, deleteArchivedSessions, archivedSessionMetadata, openConversation, viewState, close, t }) {
757
+ function ArchivedSessionsSection({ archiveSessions, sessionStore, workspaceStore, unarchiveSession, deleteSession, unarchiveSessions, deleteArchivedSessions, archivedSessionMetadata, openConversation, viewState, close, t }) {
733
758
  const sessions = (0, react.useSyncExternalStore)(sessionStore.subscribe, sessionStore.getSnapshot);
734
759
  const workspaceState = (0, react.useSyncExternalStore)(workspaceStore.subscribe, workspaceStore.getSnapshot);
760
+ const [archiveTab, setArchiveTab] = (0, react.useState)("archived");
761
+ const isArchived = archiveTab === "archived";
762
+ const [archiveTarget, setArchiveTarget] = (0, react.useState)(null);
763
+ const [archiveGroup, setArchiveGroup] = (0, react.useState)(null);
764
+ const requestArchive = (ids, group = null) => {
765
+ setError(null);
766
+ setNotice(null);
767
+ setArchiveGroup(group);
768
+ setArchiveTarget(ids);
769
+ };
770
+ const closeArchive = () => {
771
+ if (!busy) {
772
+ setArchiveTarget(null);
773
+ setArchiveGroup(null);
774
+ setError(null);
775
+ }
776
+ };
777
+ const archiveBusy = (0, react.useRef)(false);
735
778
  const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
736
779
  const [busy, setBusy] = (0, react.useState)(false);
737
780
  const [error, setError] = (0, react.useState)(null);
@@ -771,7 +814,39 @@
771
814
  (0, react.useEffect)(() => {
772
815
  if (viewState) Object.assign(viewState, { query, project, sortBy });
773
816
  }, [query, project, sortBy, viewState]);
774
- const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
817
+ const eligibleIds = (0, react.useMemo)(() => isArchived ? workspaceState.archivedSessionIds : unarchivedSessionIds(sessions.byId, workspaceState.archivedSessionIds), [isArchived, sessions.byId, workspaceState.archivedSessionIds]);
818
+ const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, eligibleIds, t("group.ungrouped")), [sessions.byId, workspaceState, eligibleIds, t]);
819
+ const switchTab = (tab) => {
820
+ if (busy || unarchivingSessionIdsRef.current.size > 0) return;
821
+ setArchiveTab(tab);
822
+ setSelectedSessionIds([]);
823
+ setArchiveTarget(null);
824
+ setArchiveGroup(null);
825
+ setError(null);
826
+ setNotice(null);
827
+ setProject("all");
828
+ setQuery("");
829
+ if (tab === "unarchived" && sortBy === "created") setSortBy("updated");
830
+ };
831
+ const confirmArchive = async () => {
832
+ if (archiveBusy.current || busy || !archiveTarget?.length) return;
833
+ archiveBusy.current = true;
834
+ setBusy(true);
835
+ setError(null);
836
+ setNotice(null);
837
+ try {
838
+ const result = await archiveSessions(archiveTarget);
839
+ setSelectedSessionIds((ids) => ids.filter((id) => !result.archivedSessionIds.includes(id)));
840
+ setNotice(t("archives.archiveSuccess", { n: result.archivedSessionIdsAdded.length }));
841
+ setArchiveTarget(null);
842
+ setArchiveGroup(null);
843
+ } catch (reason) {
844
+ setError(t("archives.archiveBatchFailed", { detail: reason instanceof Error ? reason.message : String(reason) }));
845
+ } finally {
846
+ archiveBusy.current = false;
847
+ setBusy(false);
848
+ }
849
+ };
775
850
  const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
776
851
  (0, react.useEffect)(() => {
777
852
  let cancelled = false;
@@ -801,8 +876,8 @@
801
876
  const allBatchTarget = { scope: "all" };
802
877
  const allBatchSessionIds = (0, react.useMemo)(() => deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, allBatchTarget), [workspaceState.archivedSessionIds, workspaceState.items]);
803
878
  (0, react.useEffect)(() => {
804
- setSelectedSessionIds((current) => pruneArchivedSelection(current, workspaceState.archivedSessionIds));
805
- }, [workspaceState.archivedSessionIds]);
879
+ setSelectedSessionIds((current) => pruneArchivedSelection(current, eligibleIds));
880
+ }, [eligibleIds]);
806
881
  const toggleSessionSelection = (sessionId, checked) => {
807
882
  setSelectedSessionIds((current) => toggleArchivedSelection(current, [sessionId], checked));
808
883
  };
@@ -892,20 +967,45 @@
892
967
  return (0, react_jsx_runtime.jsxs)("section", {
893
968
  className: "dsham_settings",
894
969
  "aria-label": t("archives.title"),
895
- 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", {
970
+ children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_TABS_CSS + ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS + ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS + ARCHIVE_SETTINGS_SELECTION_CSS + ARCHIVE_SETTINGS_DELETE_CONFIRM_CSS + ARCHIVE_WORKSPACE_CONFIRM_CSS }), (0, react_jsx_runtime.jsxs)("div", {
971
+ // 设置页使用独立布局容器,避免被 Codex UI 的会话 header 页签适配器识别。
896
972
  className: "dsham_settingsHeader",
897
973
  children: [(0, react_jsx_runtime.jsxs)("div", {
898
974
  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") })]
899
- }), (0, react_jsx_runtime.jsxs)("div", {
975
+ }), (0, react_jsx_runtime.jsx)("div", {
976
+ className: "dsham_archiveTabs",
977
+ role: "tablist",
978
+ "aria-label": t("archives.title"),
979
+ children: ["archived", "unarchived"].map((tab) => (0, react_jsx_runtime.jsx)("button", {
980
+ type: "button",
981
+ role: "tab",
982
+ className: "dsham_archiveTab",
983
+ id: "dsham-tab-" + tab,
984
+ "aria-controls": "dsham-archive-panel",
985
+ "aria-selected": archiveTab === tab,
986
+ tabIndex: archiveTab === tab ? 0 : -1,
987
+ disabled: busy || unarchivingSessionIds.size > 0,
988
+ onClick: () => switchTab(tab),
989
+ onKeyDown: (event) => {
990
+ if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) {
991
+ event.preventDefault();
992
+ const next = event.key === "Home" ? "archived" : event.key === "End" ? "unarchived" : isArchived ? "unarchived" : "archived";
993
+ switchTab(next);
994
+ event.currentTarget.parentElement.querySelector("#dsham-tab-" + next)?.focus();
995
+ }
996
+ },
997
+ children: t("archives.tab." + tab)
998
+ }, tab))
999
+ }), isArchived && (0, react_jsx_runtime.jsxs)("div", {
900
1000
  className: "dsham_settingsHeaderActions",
901
1001
  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")] })]
902
1002
  })]
903
- }), (0, react_jsx_runtime.jsxs)("div", {
1003
+ }), (0, react_jsx_runtime.jsxs)("div", { id: "dsham-archive-panel", role: "tabpanel", "aria-labelledby": "dsham-tab-" + archiveTab, children: [(0, react_jsx_runtime.jsxs)("div", {
904
1004
  className: "dsham_settingsToolbar",
905
1005
  children: [(0, react_jsx_runtime.jsxs)("label", {
906
1006
  className: "dsham_settingsSearch",
907
- children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })]
908
- }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-sort-filter", value: sortBy, options: [{ value: "updated", label: t("archives.sortUpdated") }, { value: "created", label: t("archives.sortCreated") }, { value: "alphabetical", label: t("archives.sortAlphabetical") }], onChange: setSortBy, "aria-label": t("archives.sortBy") }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-project-filter", value: project, options: [{ value: "all", label: t("archives.allProjects") }, ...sortedGroups.map((group) => ({ value: group.key, label: group.title }))], onChange: setProject, "aria-label": t("archives.projectFilter") })]
1007
+ 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(isArchived ? "archives.searchPlaceholder" : "archives.searchUnarchived"), "aria-label": t(isArchived ? "archives.searchPlaceholder" : "archives.searchUnarchived") })]
1008
+ }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-sort-filter", value: sortBy, options: [{ value: "updated", label: t("archives.sortUpdated") }, ...isArchived ? [{ 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") })]
909
1009
  }), groups.length > 0 && (0, react_jsx_runtime.jsx)(ArchiveSelectionToolbar, {
910
1010
  selectedCount: selectedSessionIds.length,
911
1011
  hiddenCount: selectedSessionIds.length - selectedVisibleCount,
@@ -916,28 +1016,41 @@
916
1016
  t,
917
1017
  onToggle: toggleVisibleSelection,
918
1018
  onClear: () => setSelectedSessionIds([]),
1019
+ onArchive: isArchived ? void 0 : () => requestArchive([...selectedSessionIds]),
919
1020
  onRestore: onSelectedUnarchive,
920
1021
  onDelete: () => setDeleteTarget({ kind: "batch", target: { scope: "sessions", sessionIds: selectedSessionIds }, count: selectedSessionIds.length })
921
- }), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.empty") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => {
1022
+ }), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t(isArchived ? "archives.empty" : "archives.emptyUnarchived") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => {
922
1023
  const target = archivedBatchTargetForGroup(group.key);
923
- const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
1024
+ const groupSessionIds = groups.find((item) => item.key === group.key)?.sessions.map((session) => session.id) ?? [];
1025
+ const count = isArchived ? deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length : groupSessionIds.length;
924
1026
  return (0, react_jsx_runtime.jsxs)("section", {
925
1027
  className: "dsham_settingsGroup",
926
1028
  children: [(0, react_jsx_runtime.jsxs)("div", {
927
1029
  className: "dsham_settingsGroupHeading",
928
- 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 })] })]
1030
+ 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, onArchive: isArchived ? void 0 : () => requestArchive(groupSessionIds, group), onRestore: () => onBatchUnarchive(target), onDelete: () => setDeleteTarget({ kind: "batch", target, title: group.title, count }), t })] })]
929
1031
  }), (0, react_jsx_runtime.jsx)("div", {
930
1032
  className: "dsham_settingsList",
931
1033
  children: group.sessions.map((session) => (0, react_jsx_runtime.jsxs)("article", {
932
1034
  className: "dsham_settingsRow",
933
1035
  children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, { checked: selectedSessionIdSet.has(session.id), disabled: busy, label: t("archives.selectSession", { name: displayTitle(session, t) }), onChange: (event) => toggleSessionSelection(session.id, event.target.checked) }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsContent", children: [(0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsTitle", children: displayTitle(session, t) }), (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsMeta", children: archiveTimeLabel(session.updatedAt, t) })] }), (0, react_jsx_runtime.jsxs)("div", {
934
1036
  className: "dsham_settingsActions",
935
- children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => viewConversation(session), children: t("archives.viewConversation") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => viewConversation(session, true), children: t("archives.restoreOpen") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
1037
+ children: !isArchived ? [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy, onClick: () => viewConversation(session), children: t("archives.openSession") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy, onClick: () => requestArchive([session.id]), children: t("archives.archiveSelected") })] : [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => viewConversation(session), children: t("archives.openSession") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => viewConversation(session, true), children: t("archives.restoreOpen") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("archives.restore") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
936
1038
  })]
937
1039
  }, session.id))
938
1040
  })]
939
1041
  }, group.key);
940
- }), 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, {
1042
+ }), archiveTarget === null && 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.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1043
+ open: archiveTarget !== null,
1044
+ onClose: closeArchive,
1045
+ closeLabel: t("close"),
1046
+ title: t("archives.archiveTitle", { n: archiveTarget?.length ?? 0 }),
1047
+ description: archiveGroup === null ? t("archives.archiveSelectedDesc") : t(archiveGroup.key === ARCHIVE_UNGROUPED_KEY ? "archives.archiveUngroupedDesc" : "archives.archiveProjectDesc", { name: archiveGroup.title, n: archiveTarget?.length ?? 0 }),
1048
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1049
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: closeArchive, children: t("cancel") }),
1050
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", className: "dsham_archiveWorkspaceConfirm", disabled: busy, onClick: confirmArchive, children: t("archives.archiveSelected") })
1051
+ ] }),
1052
+ children: error !== null ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsError", role: "alert", children: error }) : busy ? t("archiveWorkspace.pending") : null
1053
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
941
1054
  open: deleteTarget !== null,
942
1055
  onClose: closeDelete,
943
1056
  closeLabel: t("close"),
@@ -945,7 +1058,7 @@
945
1058
  ...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
946
1059
  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 })] }),
947
1060
  children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
948
- })]
1061
+ })] })]
949
1062
  });
950
1063
  }
951
1064
  function r(e) {
@@ -3170,12 +3283,13 @@
3170
3283
  ]
3171
3284
  });
3172
3285
  }
3286
+ const ARCHIVE_TABS_CSS = ".dsham_archiveTabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_archiveTab{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;padding:12px 2px;border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-weight:600;cursor:pointer}.dsham_archiveTab[aria-selected=true]{border-bottom-color:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary)}.dsham_archiveTab:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_archiveTab:disabled{opacity:.5;cursor:not-allowed}@container(max-width:520px){.dsham_archiveTabs{gap:16px}}";
3173
3287
  const ARCHIVE_SETTINGS_CSS = ".dsham_settings{container-type:inline-size;min-width:0;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;flex-direction:column;align-items:stretch;gap:16px;margin-bottom:16px}.dsham_settings h2{margin:0;font-size:24px;line-height:32px;font-weight:600;letter-spacing:-.4px;white-space:nowrap}.dsham_settingsIntro{margin:12px 0 0;max-width:42em;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}.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}@container(max-width:520px){.dsham_settingsHeader{margin-bottom:16px}.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}}";
3174
3288
  const ARCHIVE_SETTINGS_BATCH_CSS = ".dsham_settingsHeaderActions,.dsham_settingsGroupMeta{display:flex;align-items:center;gap:8px;flex:none;flex-wrap:wrap}.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-start}}";
3175
3289
  const ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS = ".dsham_settingsTitleRow{display:flex;align-items:center;gap:8px 12px;min-width:0;flex-wrap:wrap}.dsham_settingsLinks{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsham_settingsExternalLink{display:inline-flex;align-items:center;gap:5px;min-height:28px;padding:0 8px;color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dsham_settingsExternalLink:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsExternalLink:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_settingsExternalLink svg{flex:none}@media(max-width:720px){.dsham_settingsTitleRow{flex-wrap:wrap}}";
3176
3290
  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}";
3177
3291
  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}";
3178
- const ARCHIVE_SETTINGS_SELECTION_CSS = ".dsham_settingsSelection{margin:-8px 0 16px;padding:4px 0 12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_settingsSelectionMain{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;min-height:36px}.dsham_settingsSelectionSummary{display:flex;flex-wrap:wrap;align-items:center;gap:12px;min-width:0;max-width:100%}.dsham_settingsSelectionToggle{display:inline-flex;flex:none;white-space:nowrap;align-items:center;gap:12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;cursor:pointer}.dsham_settingsCheckbox{flex:none;width:16px;height:16px;margin:0;accent-color:var(--dsw-alias-state-business-primary,#3b82f6)}.dsham_settingsSelectionClear{flex:none;min-height:28px;padding:0 0 0 12px;border:0;border-left:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-state-business-primary,#3b82f6);font:inherit;font-size:13px;white-space:nowrap;cursor:pointer}.dsham_settingsSelectionClear:hover:not(:disabled){text-decoration:underline}.dsham_settingsSelectionActions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;max-width:100%;margin-left:auto}.dsham_settingsSelectionAction{flex:none;white-space:nowrap;min-height:32px;padding:0 14px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsSelectionAction:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsSelectionDelete{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.dsham_settingsSelectionScope{margin:8px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow-wrap:anywhere}.dsham_settingsSelectionAction:disabled,.dsham_settingsSelectionClear:disabled{cursor:not-allowed;opacity:.5}.dsham_settingsSelectionAction:focus-visible,.dsham_settingsSelectionClear:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3b82f6);outline-offset:3px}";
3292
+ const ARCHIVE_SETTINGS_SELECTION_CSS = ".dsham_settingsSelection{margin:-8px 0 16px;padding:4px 0 12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_settingsSelectionMain{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;min-height:36px}.dsham_settingsSelectionSummary{display:flex;flex-wrap:wrap;align-items:center;gap:12px;min-width:0;max-width:100%}.dsham_settingsSelectionToggle{display:inline-flex;flex:none;white-space:nowrap;align-items:center;gap:12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;cursor:pointer}.dsham_settingsCheckbox{flex:none;width:16px;height:16px;margin:0;accent-color:var(--dsw-alias-state-business-primary,#3b82f6)}.dsham_settingsSelectionClear{flex:none;min-height:28px;padding:0 0 0 12px;border:0;border-left:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-state-business-primary,#3b82f6);font:inherit;font-size:13px;white-space:nowrap;cursor:pointer}.dsham_settingsSelectionClear:hover:not(:disabled){text-decoration:underline}.dsham_settingsSelectionActions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;max-width:100%;margin-left:auto}.dsham_settingsSelectionAction{flex:none;white-space:nowrap;min-height:32px;padding:0 14px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsSelectionAction:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsSelectionArchive,.dsham_settingsSelectionDelete{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.dsham_settingsSelectionScope{margin:8px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow-wrap:anywhere}.dsham_settingsSelectionAction:disabled,.dsham_settingsSelectionClear:disabled{cursor:not-allowed;opacity:.5}.dsham_settingsSelectionAction:focus-visible,.dsham_settingsSelectionClear:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3b82f6);outline-offset:3px}";
3179
3293
  const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:0 auto!important}@media(max-width:720px){.dsham_settings{margin:0 auto!important}}";
3180
3294
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
3181
3295
  const tag = document.createElement("style");
@@ -3315,7 +3429,7 @@
3315
3429
  }) : null]
3316
3430
  });
3317
3431
  }
3318
- function ArchiveSelectionToolbar({ selectedCount, hiddenCount, allVisibleSelected, selectedVisibleCount, visibleCount, busy, t, onToggle, onClear, onRestore, onDelete }) {
3432
+ function ArchiveSelectionToolbar({ selectedCount, hiddenCount, allVisibleSelected, selectedVisibleCount, visibleCount, busy, t, onToggle, onClear, onRestore, onDelete, onArchive }) {
3319
3433
  const hasSelection = selectedCount > 0;
3320
3434
  return (0, react_jsx_runtime.jsxs)("div", {
3321
3435
  className: "dsham_settingsSelection",
@@ -3336,7 +3450,7 @@
3336
3450
  }), hasSelection && (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionClear", disabled: busy, "aria-label": t("archives.clearSelection"), onClick: onClear, children: t("archives.clearSelectionShort") })]
3337
3451
  }), hasSelection && (0, react_jsx_runtime.jsxs)("div", {
3338
3452
  className: "dsham_settingsSelectionActions",
3339
- children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy, "aria-label": t("archives.restoreSelected"), onClick: onRestore, children: t("archives.restoreSelectionShort") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction dsham_settingsSelectionDelete", disabled: busy, "aria-label": t("archives.deleteSelected"), onClick: onDelete, children: t("archives.deleteSelectionShort") })]
3453
+ children: onArchive ? [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction dsham_settingsSelectionArchive", disabled: busy, onClick: onArchive, children: t("archives.archiveSelected") })] : [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy, "aria-label": t("archives.restoreSelected"), onClick: onRestore, children: t("archives.restore") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction dsham_settingsSelectionDelete", disabled: busy, "aria-label": t("archives.deleteSelected"), onClick: onDelete, children: t("archives.deleteSelectionShort") })]
3340
3454
  })]
3341
3455
  }), hiddenCount > 0 && (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsSelectionScope", role: "status", children: t("archives.selectionHiddenHint", { hidden: hiddenCount }) })]
3342
3456
  });
@@ -3356,10 +3470,15 @@
3356
3470
  onChange
3357
3471
  });
3358
3472
  }
3359
- function ArchivedGroupActions({ group, busy, onRestore, onDelete, t }) {
3473
+ function ArchivedGroupActions({ group, busy, onArchive, onRestore, onDelete, t }) {
3360
3474
  const [open, setOpen] = (0, react.useState)(false);
3361
3475
  const ungrouped = group.key === ARCHIVE_UNGROUPED_KEY;
3362
- const items = [{
3476
+ const items = onArchive ? [{
3477
+ id: "archive",
3478
+ label: t(ungrouped ? "archives.archiveUngrouped" : "archives.archiveProject"),
3479
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 }),
3480
+ danger: true
3481
+ }] : [{
3363
3482
  id: "restore",
3364
3483
  label: t(ungrouped ? "archives.restoreUngrouped" : "archives.restoreProject"),
3365
3484
  icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {})
@@ -3375,7 +3494,9 @@
3375
3494
  items,
3376
3495
  onSelect: (id) => {
3377
3496
  setOpen(false);
3378
- if (id === "restore") onRestore();
3497
+ if (busy) return;
3498
+ if (id === "archive") onArchive?.();
3499
+ else if (id === "restore") onRestore();
3379
3500
  else if (id === "delete") onDelete();
3380
3501
  },
3381
3502
  portal: true,
@@ -3439,6 +3560,10 @@
3439
3560
  };
3440
3561
  return { kind: "notice", message: t("archives.deleteSuccess", { n: deleted }) };
3441
3562
  }
3563
+ function unarchivedSessionIds(byId, archivedSessionIds) {
3564
+ const archived = new Set(archivedSessionIds);
3565
+ return Object.values(byId).filter((session) => session.origin !== "subagent" && !session.blank && !archived.has(session.id)).map((session) => session.id);
3566
+ }
3442
3567
  function deriveArchivedGroups(byId, items, archivedSessionIds, ungroupedLabel) {
3443
3568
  const byWorkspace = items.map((workspace) => ({
3444
3569
  key: workspace.workspaceId,
@@ -3480,14 +3605,27 @@
3480
3605
  });
3481
3606
  }
3482
3607
  const zh = {
3483
- "archives.viewConversation": "\u67E5\u770B\u5BF9\u8BDD",
3608
+ "archives.archiveProject": "\u5F52\u6863\u8BE5\u9879\u76EE\u7684\u5168\u90E8\u804A\u5929",
3609
+ "archives.archiveUngrouped": "\u5F52\u6863\u5168\u90E8\u672A\u5206\u7EC4\u804A\u5929",
3610
+ "archives.archiveProjectDesc": "\u5C06\u201C{name}\u201D\u4E2D\u5168\u90E8 {n} \u6761\u672A\u5F52\u6863\u4F1A\u8BDD\u5F52\u6863\uFF0C\u4E0D\u53D7\u5F53\u524D\u641C\u7D22\u7B5B\u9009\u5F71\u54CD\u3002\u4E4B\u540E\u53EF\u5728\u201C\u5DF2\u5F52\u6863\u201D\u4E2D\u6062\u590D\u3002",
3611
+ "archives.archiveUngroupedDesc": "\u5C06\u5168\u90E8 {n} \u6761\u672A\u5206\u7EC4\u7684\u672A\u5F52\u6863\u4F1A\u8BDD\u5F52\u6863\uFF0C\u4E0D\u53D7\u5F53\u524D\u641C\u7D22\u7B5B\u9009\u5F71\u54CD\u3002\u4E4B\u540E\u53EF\u5728\u201C\u5DF2\u5F52\u6863\u201D\u4E2D\u6062\u590D\u3002",
3612
+ "archives.tab.archived": "\u5DF2\u5F52\u6863",
3613
+ "archives.tab.unarchived": "\u672A\u5F52\u6863",
3614
+ "archives.searchUnarchived": "\u641C\u7D22\u672A\u5F52\u6863\u804A\u5929",
3615
+ "archives.emptyUnarchived": "\u6682\u65E0\u672A\u5F52\u6863\u4F1A\u8BDD\u3002",
3616
+ "archives.archiveSelected": "\u5F52\u6863",
3617
+ "archives.openSession": "\u6253\u5F00\u4F1A\u8BDD",
3618
+ "archives.archiveSelectedDesc": "\u5C06\u9009\u4E2D\u7684\u4F1A\u8BDD\u5F52\u6863\uFF0C\u5305\u542B\u5F53\u524D\u7B5B\u9009\u9690\u85CF\u7684\u5DF2\u9009\u9879\u3002\u4E4B\u540E\u53EF\u5728\u201C\u5DF2\u5F52\u6863\u201D\u4E2D\u67E5\u770B\u548C\u6062\u590D\u3002",
3619
+ "archives.archiveSuccess": "\u5DF2\u5F52\u6863 {n} \u6761\u4F1A\u8BDD\u3002",
3620
+ "archives.archiveBatchFailed": "\u6279\u91CF\u5F52\u6863\u5931\u8D25\uFF1A{detail}",
3484
3621
  "archives.navigationUnavailable": "\u5F53\u524D\u5BBF\u4E3B\u65E0\u6CD5\u4FDD\u6301\u5F52\u6863\u5BF9\u8BDD\uFF0C\u8BF7\u4F7F\u7528\u201C\u6062\u590D\u5E76\u6253\u5F00\u201D\u3002",
3485
3622
  "archives.sessionNotRetained": "\u5BBF\u4E3B\u672A\u4FDD\u7559\u76EE\u6807\u4F1A\u8BDD\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u6216\u6062\u590D\u540E\u518D\u8BD5\u3002",
3486
3623
  "archives.restoreOpen": "\u6062\u590D\u5E76\u6253\u5F00",
3487
3624
  "archives.selectedCount": "\u5DF2\u9009 {n} \u6761",
3488
3625
  "archives.selectionHiddenHint": "\u542B\u5F53\u524D\u672A\u663E\u793A\u7684 {hidden} \u6761\uFF0C\u4E5F\u5C06\u53C2\u4E0E\u64CD\u4F5C",
3489
3626
  "archives.clearSelectionShort": "\u6E05\u7A7A",
3490
- "archives.restoreSelectionShort": "\u6062\u590D",
3627
+ "archives.restore": "\u6062\u590D",
3628
+ "archives.archiveTitle": "\u5F52\u6863 {n} \u4E2A\u804A\u5929\uFF1F",
3491
3629
  "archives.deleteSelectionShort": "\u5220\u9664",
3492
3630
  "archives.clearSelection": "\u6E05\u7A7A\u9009\u62E9",
3493
3631
  "group.ungrouped": "\u672A\u5206\u7EC4",
@@ -3519,7 +3657,7 @@
3519
3657
  "archived.badge": "\u5F52\u6863\u4F1A\u8BDD",
3520
3658
  "archived.notOpenable": "\u5DF2\u5F52\u6863\uFF0C\u53D6\u6D88\u5F52\u6863\u540E\u53EF\u7EE7\u7EED\u5BF9\u8BDD",
3521
3659
  "archives.title": "\u5F52\u6863\u4F1A\u8BDD",
3522
- "archives.description": "\u7BA1\u7406\u5DF2\u5F52\u6863\u7684\u4F1A\u8BDD\u3002",
3660
+ "archives.description": "\u7BA1\u7406\u5DF2\u5F52\u6863\u4F1A\u8BDD\uFF0C\u6216\u9009\u62E9\u672A\u5F52\u6863\u4F1A\u8BDD\u8FDB\u884C\u6279\u91CF\u5F52\u6863\u3002",
3523
3661
  "archives.viewProject": "GitHub",
3524
3662
  "archives.feedback": "\u95EE\u9898\u53CD\u9988",
3525
3663
  "archives.empty": "\u6682\u65E0\u5DF2\u5F52\u6863\u4F1A\u8BDD",
@@ -3615,14 +3753,27 @@
3615
3753
  "time.ago": "{t}\u524D"
3616
3754
  };
3617
3755
  const en = {
3618
- "archives.viewConversation": "View conversation",
3756
+ "archives.archiveProject": "Archive all chats in this project",
3757
+ "archives.archiveUngrouped": "Archive all ungrouped chats",
3758
+ "archives.archiveProjectDesc": "Archive all {n} unarchived sessions in \u201C{name}\u201D, regardless of the current search filter. You can restore them in Archived.",
3759
+ "archives.archiveUngroupedDesc": "Archive all {n} ungrouped, unarchived sessions, regardless of the current search filter. You can restore them in Archived.",
3760
+ "archives.tab.archived": "Archived",
3761
+ "archives.tab.unarchived": "Unarchived",
3762
+ "archives.searchUnarchived": "Search unarchived chats",
3763
+ "archives.emptyUnarchived": "No unarchived sessions.",
3764
+ "archives.archiveSelected": "Archive",
3765
+ "archives.openSession": "Open session",
3766
+ "archives.archiveSelectedDesc": "Archive the selected sessions, including selections hidden by the current filter. You can view and restore them in Archived.",
3767
+ "archives.archiveSuccess": "Archived {n} sessions.",
3768
+ "archives.archiveBatchFailed": "Batch archive failed: {detail}",
3619
3769
  "archives.navigationUnavailable": "This host cannot keep the conversation archived while opening it. Use \u201CRestore and open\u201D instead.",
3620
3770
  "archives.sessionNotRetained": "The host did not keep the requested conversation open. Try again, or restore it before opening.",
3621
3771
  "archives.restoreOpen": "Restore and open",
3622
3772
  "archives.selectedCount": "{n} selected",
3623
3773
  "archives.selectionHiddenHint": "Includes {hidden} hidden by the current filter; actions apply to them too.",
3624
3774
  "archives.clearSelectionShort": "Clear",
3625
- "archives.restoreSelectionShort": "Restore",
3775
+ "archives.restore": "Restore",
3776
+ "archives.archiveTitle": "Archive {n} chats?",
3626
3777
  "archives.deleteSelectionShort": "Delete",
3627
3778
  "archives.clearSelection": "Clear selection",
3628
3779
  "group.ungrouped": "Ungrouped",
@@ -3654,7 +3805,7 @@
3654
3805
  "archived.badge": "Archived sessions",
3655
3806
  "archived.notOpenable": "This session is archived. Unarchive it to continue the conversation.",
3656
3807
  "archives.title": "Archived sessions",
3657
- "archives.description": "Manage archived sessions.",
3808
+ "archives.description": "Manage archived sessions or select unarchived sessions to archive in bulk.",
3658
3809
  "archives.viewProject": "GitHub",
3659
3810
  "archives.feedback": "Issues",
3660
3811
  "archives.empty": "No archived sessions.",
@@ -3843,6 +3994,14 @@
3843
3994
  if (!result.ok) throw new Error(result.error.message);
3844
3995
  await refreshSessionList();
3845
3996
  };
3997
+ const archiveSessions = async (sessionIds) => {
3998
+ const registry = ctx.get("remote.workspaceRegistry");
3999
+ if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
4000
+ const result = await registry.archiveSessions(sessionIds);
4001
+ if (!result.ok) throw new Error(result.error.message);
4002
+ await refreshSessionList();
4003
+ return result.value;
4004
+ };
3846
4005
  const unarchiveSessions = async (target) => {
3847
4006
  const registry = ctx.get("remote.workspaceRegistry");
3848
4007
  if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
@@ -3941,6 +4100,7 @@
3941
4100
  icon: "archive",
3942
4101
  locale: NS,
3943
4102
  inject: () => ({
4103
+ archiveSessions,
3944
4104
  sessionStore: bindObservable(ctx.sessions.list),
3945
4105
  workspaceStore: bindObservable(ctx.workspaces.list),
3946
4106
  unarchiveSession,
@@ -3955,6 +4115,7 @@
3955
4115
  }, ArchivedSessionsSection));
3956
4116
  }
3957
4117
  exports.__test = {
4118
+ unarchivedSessionIds,
3958
4119
  ArchiveSelectionToolbar,
3959
4120
  ArchivedSessionsSection,
3960
4121
  zh,
package/lib/workspace.js CHANGED
@@ -118,6 +118,12 @@ const unarchivedBatchSchema = {
118
118
  return value;
119
119
  }
120
120
  };
121
+ const archiveSessionIdsSchema = {
122
+ parse(value) {
123
+ if (!Array.isArray(value)) throw new TypeError("sessionIds must be an array");
124
+ return [...new Set(value.map((id) => sessionIdSchema.parse(id)))];
125
+ }
126
+ };
121
127
  const archivedWorkspaceBatchSchema = {
122
128
  parse(value) {
123
129
  if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -249,6 +255,35 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
249
255
  column: 1
250
256
  }
251
257
  },
258
+ {
259
+ id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveSessions",
260
+ service: "workspaceRegistry",
261
+ namespace: "workspaceRegistry",
262
+ method: "archiveSessions",
263
+ invocation: { kind: "direct" },
264
+ parameters: [
265
+ {
266
+ name: "sessionIds",
267
+ wire: "sessionIds",
268
+ source: "json",
269
+ codec: {
270
+ mode: "strict",
271
+ typeSymbol: "@michengai/dsh-archive-manager/types#ArchiveSessionIds",
272
+ schema: archiveSessionIdsSchema
273
+ }
274
+ }
275
+ ],
276
+ result: {
277
+ mode: "strict",
278
+ typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedWorkspaceBatch",
279
+ schema: archivedWorkspaceBatchSchema
280
+ },
281
+ sourceLocation: {
282
+ file: "@michengai/dsh-archive-manager/lib/workspace.js",
283
+ line: 1,
284
+ column: 1
285
+ }
286
+ },
252
287
  {
253
288
  id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveWorkspaceSessions",
254
289
  service: "workspaceRegistry",
@@ -365,6 +400,7 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
365
400
  markRemoteMethod(this, "deleteSession");
366
401
  markRemoteMethod(this, "unarchiveSessions");
367
402
  markRemoteMethod(this, "archiveWorkspaceSessions");
403
+ markRemoteMethod(this, "archiveSessions");
368
404
  markRemoteMethod(this, "deleteArchivedSessions");
369
405
  markRemoteMethod(this, "archivedSessionMetadata");
370
406
  registerHostRemote(this.ctx);
@@ -494,6 +530,21 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
494
530
  return { archivedSessionIds: [...next.archivedSessionIds] };
495
531
  });
496
532
  }
533
+ /** 按明确 ID 跨工作区归档;先校验整批,单次写入,重复归档不新增标记。 */
534
+ async archiveSessions(sessionIds) {
535
+ return this.enqueueOperation(async () => {
536
+ const ids = archiveSessionIdsSchema.parse(sessionIds);
537
+ const state = this.requireState();
538
+ const archived = new Set(state.archivedSessionIds);
539
+ const archivedSessionIdsAdded = ids.filter((id) => !archived.has(id));
540
+ for (const id of archivedSessionIdsAdded) {
541
+ if (!await this.sessionKnown(id)) throw new ArchiveUnknownSessionError(id);
542
+ }
543
+ const next = { ...state, archivedSessionIds: [...state.archivedSessionIds, ...archivedSessionIdsAdded] };
544
+ if (archivedSessionIdsAdded.length > 0) await this.setState(next);
545
+ return { archivedSessionIds: [...next.archivedSessionIds], archivedSessionIdsAdded };
546
+ });
547
+ }
497
548
  /**
498
549
  * 将一个工作区内所有会话加入归档集合。先确认所有待归档会话仍存在,
499
550
  * 再执行单次状态写入,因此未知会话不会导致项目只归档一部分。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "description": "NPM-installable DSH Web plugin for managing archived sessions.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",