@michengai/dsh-archive-manager 0.1.40 → 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,17 @@
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
+
13
+ ## 0.1.41 - 2026-09-14
14
+
15
+ - Improve the archived-session bulk action bar with grouped selection details and actions, fixing wrapped button labels in narrow content areas.
16
+ - Simplify the bar when nothing is selected and show cross-filter selection counts and scope separately so hidden selections remain clear.
17
+
7
18
  ## 0.1.40 - 2026-09-13
8
19
 
9
20
  - Open archived sessions in the native DSH conversation view and continue chatting while keeping them archived, or restore and open them.
@@ -4,6 +4,17 @@
4
4
 
5
5
  以下发布说明会持续保留;新增版本时不再删除较早记录。
6
6
 
7
+ ## 0.1.42 - 2026-09-14
8
+
9
+ - 新增「已归档 / 未归档」页签,默认展示已归档;支持跨项目选择会话批量归档,也可通过项目或未分组菜单归档该组全部聊天。
10
+ - 统一会话操作文案,提升批量归档按钮与菜单的辨识度;归档失败时保留确认框并显示错误,可直接重试,同时清理过期提示。
11
+ - 新增 Typert `workspaceRegistry/archiveSessions` 接口,接受 `{ sessionIds: string[] }` 批量归档跨项目会话,支持去重与幂等,无效会话导致整批拒绝。
12
+
13
+ ## 0.1.41 - 2026-09-14
14
+
15
+ - 优化归档会话的批量操作栏:选择摘要与操作分组显示,修复窄内容区内按钮文字折行的问题。
16
+ - 未选择时简化操作栏;跨筛选选择的数量和影响范围独立显示,避免遗漏不可见的已选会话。
17
+
7
18
  ## 0.1.40 - 2026-09-13
8
19
 
9
20
  - 支持直接打开归档会话,在 DSH 原生页面查看完整对话并保持归档继续聊天,也可恢复后打开。
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
@@ -139,9 +145,24 @@ Restart DSH Web and hard-refresh your browser afterward. Edit [src](src), not th
139
145
 
140
146
  </details>
141
147
 
142
- ## Related projects
148
+ ## DSH product ecosystem
149
+
150
+ For a desktop workbench, download [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases). Existing [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) installations can add plugins as needed by following each project's README. Below are 11 first-party plugins; consult the corresponding desktop release notes and bundled catalog for what that version includes.
151
+
152
+ | Plugin | What you can do |
153
+ | --- | --- |
154
+ | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | Organize projects and conversations, search tasks, and navigate chat turns |
155
+ | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | Choose and summon specialists for your task |
156
+ | [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | Find, enable, create, and import local skills |
157
+ | [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | Search, restore, or clean up archived conversations |
158
+ | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | Send tasks and receive replies through messaging platforms |
159
+ | [Automation](https://github.com/MichengAI/dsh-automation) | Schedule tasks and review each run |
160
+ | [BTW](https://github.com/MichengAI/dsh-btw) | Ask side questions without interrupting the main task |
161
+ | [Simplify](https://github.com/MichengAI/dsh-simplify) | Use `/simplify` to improve code within your Git changes |
162
+ | [PUA](https://github.com/MichengAI/dsh-pua) | Guide the Agent to try new approaches after failures, investigate causes, and verify results before completion |
163
+ | [Code Review](https://github.com/MichengAI/dsh-code-review) | Use `/review` to request an independent Agent code review and receive the report in the current conversation |
164
+ | [Codex Pet](https://github.com/MichengAI/dsh-codex-pet) | View conversation notifications and respond to tool approvals and questions through a desktop pet |
143
165
 
144
- [DSH Codex UI](https://github.com/MichengAI/dsh-codex-ui) provides project and conversation management. For a desktop workbench, see [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop).
145
166
 
146
167
  ## License
147
168
 
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
  ## 更新
@@ -139,9 +145,24 @@ dsh plugin --profile web add .
139
145
 
140
146
  </details>
141
147
 
142
- ## 相关项目
148
+ ## DSH 产品生态
149
+
150
+ 想使用桌面工作台,可下载 [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases);已有 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 环境,可按各项目 README 按需安装。以下列出 11 个自研插件;桌面端实际随附范围以对应版本的发行说明和内置清单为准。
151
+
152
+ | 插件 | 你可以用它做什么 |
153
+ | --- | --- |
154
+ | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | 整理项目与会话、搜索任务、跳转对话轮次 |
155
+ | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | 按任务选择并召唤专业角色 |
156
+ | [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | 统一查找、启停、创建和导入本机技能 |
157
+ | [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | 搜索、恢复或清理已归档会话 |
158
+ | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | 从消息平台下任务、收回复 |
159
+ | [Automation](https://github.com/MichengAI/dsh-automation) | 按计划执行任务,查看每次运行的结果 |
160
+ | [BTW](https://github.com/MichengAI/dsh-btw) | 在当前上下文中临时旁问,不打断主任务 |
161
+ | [Simplify](https://github.com/MichengAI/dsh-simplify) | 用 `/simplify` 整理 Git 改动范围内的代码 |
162
+ | [PUA](https://github.com/MichengAI/dsh-pua) | 引导 Agent 在失败时换方法、查原因,并在完成前验证结果 |
163
+ | [Code Review](https://github.com/MichengAI/dsh-code-review) | 用 `/review` 发起独立 Agent 代码审查,在当前会话接收报告 |
164
+ | [Codex Pet](https://github.com/MichengAI/dsh-codex-pet) | 通过桌面宠物查看会话提醒、处理工具审批和问题回答 |
143
165
 
144
- [DSH Codex UI](https://github.com/MichengAI/dsh-codex-ui) 提供项目与会话管理界面;想使用桌面工作台,可查看 [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop)。
145
166
 
146
167
  ## 许可证
147
168
 
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,46 +967,90 @@
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") })]
909
- }), groups.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
910
- className: "dsham_settingsSelection",
911
- children: [(0, react_jsx_runtime.jsxs)("label", {
912
- className: "dsham_settingsSelectionToggle",
913
- children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, { checked: allVisibleSelected, indeterminate: selectedVisibleCount > 0 && !allVisibleSelected, disabled: busy || visibleSessionIds.length === 0, label: t("archives.selectAllFiltered"), onChange: (event) => toggleVisibleSelection(event.target.checked) }), t("archives.selectAllFiltered")]
914
- }), (0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsSelectionCount", children: t("archives.selectedScope", { n: selectedSessionIds.length, hidden: selectedSessionIds.length - selectedVisibleCount }) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy || selectedSessionIds.length === 0, onClick: () => setSelectedSessionIds([]), children: t("archives.clearSelection") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy || selectedSessionIds.length === 0, onClick: onSelectedUnarchive, children: t("archives.restoreSelected") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction dsham_settingsSelectionDelete", disabled: busy || selectedSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: { scope: "sessions", sessionIds: selectedSessionIds }, count: selectedSessionIds.length }), children: t("archives.deleteSelected") })]
915
- }), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.empty") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => {
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") })]
1009
+ }), groups.length > 0 && (0, react_jsx_runtime.jsx)(ArchiveSelectionToolbar, {
1010
+ selectedCount: selectedSessionIds.length,
1011
+ hiddenCount: selectedSessionIds.length - selectedVisibleCount,
1012
+ allVisibleSelected,
1013
+ selectedVisibleCount,
1014
+ visibleCount: visibleSessionIds.length,
1015
+ busy,
1016
+ t,
1017
+ onToggle: toggleVisibleSelection,
1018
+ onClear: () => setSelectedSessionIds([]),
1019
+ onArchive: isArchived ? void 0 : () => requestArchive([...selectedSessionIds]),
1020
+ onRestore: onSelectedUnarchive,
1021
+ onDelete: () => setDeleteTarget({ kind: "batch", target: { scope: "sessions", sessionIds: selectedSessionIds }, count: selectedSessionIds.length })
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) => {
916
1023
  const target = archivedBatchTargetForGroup(group.key);
917
- 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;
918
1026
  return (0, react_jsx_runtime.jsxs)("section", {
919
1027
  className: "dsham_settingsGroup",
920
1028
  children: [(0, react_jsx_runtime.jsxs)("div", {
921
1029
  className: "dsham_settingsGroupHeading",
922
- 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 })] })]
923
1031
  }), (0, react_jsx_runtime.jsx)("div", {
924
1032
  className: "dsham_settingsList",
925
1033
  children: group.sessions.map((session) => (0, react_jsx_runtime.jsxs)("article", {
926
1034
  className: "dsham_settingsRow",
927
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", {
928
1036
  className: "dsham_settingsActions",
929
- children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => viewConversation(session), children: t("archives.viewConversation") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => viewConversation(session, true), children: t("archives.restoreOpen") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
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, {}) })]
930
1038
  })]
931
1039
  }, session.id))
932
1040
  })]
933
1041
  }, group.key);
934
- }), 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, {
935
1054
  open: deleteTarget !== null,
936
1055
  onClose: closeDelete,
937
1056
  closeLabel: t("close"),
@@ -939,7 +1058,7 @@
939
1058
  ...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
940
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 })] }),
941
1060
  children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
942
- })]
1061
+ })] })]
943
1062
  });
944
1063
  }
945
1064
  function r(e) {
@@ -3164,12 +3283,13 @@
3164
3283
  ]
3165
3284
  });
3166
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}}";
3167
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}}";
3168
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}}";
3169
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}}";
3170
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}";
3171
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}";
3172
- 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}}";
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}";
3173
3293
  const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:0 auto!important}@media(max-width:720px){.dsham_settings{margin:0 auto!important}}";
3174
3294
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
3175
3295
  const tag = document.createElement("style");
@@ -3309,6 +3429,32 @@
3309
3429
  }) : null]
3310
3430
  });
3311
3431
  }
3432
+ function ArchiveSelectionToolbar({ selectedCount, hiddenCount, allVisibleSelected, selectedVisibleCount, visibleCount, busy, t, onToggle, onClear, onRestore, onDelete, onArchive }) {
3433
+ const hasSelection = selectedCount > 0;
3434
+ return (0, react_jsx_runtime.jsxs)("div", {
3435
+ className: "dsham_settingsSelection",
3436
+ children: [(0, react_jsx_runtime.jsxs)("div", {
3437
+ className: "dsham_settingsSelectionMain",
3438
+ children: [(0, react_jsx_runtime.jsxs)("div", {
3439
+ className: "dsham_settingsSelectionSummary",
3440
+ children: [(0, react_jsx_runtime.jsxs)("label", {
3441
+ className: "dsham_settingsSelectionToggle",
3442
+ title: t("archives.selectAllFiltered"),
3443
+ children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, {
3444
+ checked: allVisibleSelected,
3445
+ indeterminate: selectedVisibleCount > 0 && !allVisibleSelected,
3446
+ disabled: busy || visibleCount === 0,
3447
+ label: t("archives.selectAllFiltered"),
3448
+ onChange: (event) => onToggle(event.target.checked)
3449
+ }), (0, react_jsx_runtime.jsx)("span", { "aria-live": "polite", children: hasSelection ? t("archives.selectedCount", { n: selectedCount }) : t("archives.selectAllFiltered") })]
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") })]
3451
+ }), hasSelection && (0, react_jsx_runtime.jsxs)("div", {
3452
+ className: "dsham_settingsSelectionActions",
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") })]
3454
+ })]
3455
+ }), hiddenCount > 0 && (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsSelectionScope", role: "status", children: t("archives.selectionHiddenHint", { hidden: hiddenCount }) })]
3456
+ });
3457
+ }
3312
3458
  function ArchiveSelectionCheckbox({ checked, indeterminate = false, disabled = false, label, onChange }) {
3313
3459
  const inputRef = (0, react.useRef)(null);
3314
3460
  (0, react.useEffect)(() => {
@@ -3324,10 +3470,15 @@
3324
3470
  onChange
3325
3471
  });
3326
3472
  }
3327
- function ArchivedGroupActions({ group, busy, onRestore, onDelete, t }) {
3473
+ function ArchivedGroupActions({ group, busy, onArchive, onRestore, onDelete, t }) {
3328
3474
  const [open, setOpen] = (0, react.useState)(false);
3329
3475
  const ungrouped = group.key === ARCHIVE_UNGROUPED_KEY;
3330
- 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
+ }] : [{
3331
3482
  id: "restore",
3332
3483
  label: t(ungrouped ? "archives.restoreUngrouped" : "archives.restoreProject"),
3333
3484
  icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {})
@@ -3343,7 +3494,9 @@
3343
3494
  items,
3344
3495
  onSelect: (id) => {
3345
3496
  setOpen(false);
3346
- if (id === "restore") onRestore();
3497
+ if (busy) return;
3498
+ if (id === "archive") onArchive?.();
3499
+ else if (id === "restore") onRestore();
3347
3500
  else if (id === "delete") onDelete();
3348
3501
  },
3349
3502
  portal: true,
@@ -3407,6 +3560,10 @@
3407
3560
  };
3408
3561
  return { kind: "notice", message: t("archives.deleteSuccess", { n: deleted }) };
3409
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
+ }
3410
3567
  function deriveArchivedGroups(byId, items, archivedSessionIds, ungroupedLabel) {
3411
3568
  const byWorkspace = items.map((workspace) => ({
3412
3569
  key: workspace.workspaceId,
@@ -3448,11 +3605,28 @@
3448
3605
  });
3449
3606
  }
3450
3607
  const zh = {
3451
- "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}",
3452
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",
3453
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",
3454
3623
  "archives.restoreOpen": "\u6062\u590D\u5E76\u6253\u5F00",
3455
- "archives.selectedScope": "\u5DF2\u9009 {n} \u6761\uFF0C\u5176\u4E2D {hidden} \u6761\u4E0D\u5728\u5F53\u524D\u7ED3\u679C",
3624
+ "archives.selectedCount": "\u5DF2\u9009 {n} \u6761",
3625
+ "archives.selectionHiddenHint": "\u542B\u5F53\u524D\u672A\u663E\u793A\u7684 {hidden} \u6761\uFF0C\u4E5F\u5C06\u53C2\u4E0E\u64CD\u4F5C",
3626
+ "archives.clearSelectionShort": "\u6E05\u7A7A",
3627
+ "archives.restore": "\u6062\u590D",
3628
+ "archives.archiveTitle": "\u5F52\u6863 {n} \u4E2A\u804A\u5929\uFF1F",
3629
+ "archives.deleteSelectionShort": "\u5220\u9664",
3456
3630
  "archives.clearSelection": "\u6E05\u7A7A\u9009\u62E9",
3457
3631
  "group.ungrouped": "\u672A\u5206\u7EC4",
3458
3632
  "session.new": "\u65B0\u4F1A\u8BDD",
@@ -3483,7 +3657,7 @@
3483
3657
  "archived.badge": "\u5F52\u6863\u4F1A\u8BDD",
3484
3658
  "archived.notOpenable": "\u5DF2\u5F52\u6863\uFF0C\u53D6\u6D88\u5F52\u6863\u540E\u53EF\u7EE7\u7EED\u5BF9\u8BDD",
3485
3659
  "archives.title": "\u5F52\u6863\u4F1A\u8BDD",
3486
- "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",
3487
3661
  "archives.viewProject": "GitHub",
3488
3662
  "archives.feedback": "\u95EE\u9898\u53CD\u9988",
3489
3663
  "archives.empty": "\u6682\u65E0\u5DF2\u5F52\u6863\u4F1A\u8BDD",
@@ -3579,11 +3753,28 @@
3579
3753
  "time.ago": "{t}\u524D"
3580
3754
  };
3581
3755
  const en = {
3582
- "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}",
3583
3769
  "archives.navigationUnavailable": "This host cannot keep the conversation archived while opening it. Use \u201CRestore and open\u201D instead.",
3584
3770
  "archives.sessionNotRetained": "The host did not keep the requested conversation open. Try again, or restore it before opening.",
3585
3771
  "archives.restoreOpen": "Restore and open",
3586
- "archives.selectedScope": "{n} selected, {hidden} outside current results",
3772
+ "archives.selectedCount": "{n} selected",
3773
+ "archives.selectionHiddenHint": "Includes {hidden} hidden by the current filter; actions apply to them too.",
3774
+ "archives.clearSelectionShort": "Clear",
3775
+ "archives.restore": "Restore",
3776
+ "archives.archiveTitle": "Archive {n} chats?",
3777
+ "archives.deleteSelectionShort": "Delete",
3587
3778
  "archives.clearSelection": "Clear selection",
3588
3779
  "group.ungrouped": "Ungrouped",
3589
3780
  "session.new": "New Session",
@@ -3614,7 +3805,7 @@
3614
3805
  "archived.badge": "Archived sessions",
3615
3806
  "archived.notOpenable": "This session is archived. Unarchive it to continue the conversation.",
3616
3807
  "archives.title": "Archived sessions",
3617
- "archives.description": "Manage archived sessions.",
3808
+ "archives.description": "Manage archived sessions or select unarchived sessions to archive in bulk.",
3618
3809
  "archives.viewProject": "GitHub",
3619
3810
  "archives.feedback": "Issues",
3620
3811
  "archives.empty": "No archived sessions.",
@@ -3803,6 +3994,14 @@
3803
3994
  if (!result.ok) throw new Error(result.error.message);
3804
3995
  await refreshSessionList();
3805
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
+ };
3806
4005
  const unarchiveSessions = async (target) => {
3807
4006
  const registry = ctx.get("remote.workspaceRegistry");
3808
4007
  if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
@@ -3901,6 +4100,7 @@
3901
4100
  icon: "archive",
3902
4101
  locale: NS,
3903
4102
  inject: () => ({
4103
+ archiveSessions,
3904
4104
  sessionStore: bindObservable(ctx.sessions.list),
3905
4105
  workspaceStore: bindObservable(ctx.workspaces.list),
3906
4106
  unarchiveSession,
@@ -3915,6 +4115,8 @@
3915
4115
  }, ArchivedSessionsSection));
3916
4116
  }
3917
4117
  exports.__test = {
4118
+ unarchivedSessionIds,
4119
+ ArchiveSelectionToolbar,
3918
4120
  ArchivedSessionsSection,
3919
4121
  zh,
3920
4122
  displayTitle,
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.40",
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",