dsh-native-session-delete 1.0.7 → 1.1.1

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/lib/client.js CHANGED
@@ -1,4 +1,4 @@
1
- // Modified from @deepseek-ai/dsh-client-ui-workspace 0.1.1-rc.2 by DSH Native Session Delete. See THIRD_PARTY_NOTICES.md.
1
+ // Modified from @deepseek-ai/dsh-client-ui-workspace 0.1.1-rc.2 by DSH Native Session Manager. See THIRD_PARTY_NOTICES.md.
2
2
  window.__ModuleLoader__.load({
3
3
  id: "dsh-native-session-delete",
4
4
  factory: (require) => {
@@ -1656,7 +1656,7 @@ window.__ModuleLoader__.load({
1656
1656
  * @param props - composed slot props (shell owner share + store + injected actions).
1657
1657
  * @returns the region element tree.
1658
1658
  */
1659
- function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, deleteSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostDescription, renderSlot, t }) {
1659
+ function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, deleteSession, restoreSession, searchArchivedSessions, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostDescription, renderSlot, t }) {
1660
1660
  const home = useHostDescription((description) => description?.home);
1661
1661
  const workspaces = useWorkspaces((state) => state.items);
1662
1662
  const workspacePhase = useWorkspaces((state) => state.phase);
@@ -1858,6 +1858,60 @@ window.__ModuleLoader__.load({
1858
1858
  console.warn("session archive rejected:", reason);
1859
1859
  });
1860
1860
  };
1861
+ const archiveSessionList = useSessions((state) => state);
1862
+ const [archiveManagerOpen, setArchiveManagerOpen] = (0, react.useState)(false);
1863
+ const [archiveQuery, setArchiveQuery] = (0, react.useState)("");
1864
+ const [archiveSearch, setArchiveSearch] = (0, react.useState)({ query: "", status: "idle", items: [], hasMore: false });
1865
+ const [archiveBusyId, setArchiveBusyId] = (0, react.useState)(null);
1866
+ const [archiveError, setArchiveError] = (0, react.useState)(null);
1867
+ const normalizedArchiveQuery = archiveQuery.trim();
1868
+ (0, react.useEffect)(() => {
1869
+ if (!archiveManagerOpen || normalizedArchiveQuery === "") {
1870
+ setArchiveSearch({ query: "", status: "idle", items: [], hasMore: false });
1871
+ return;
1872
+ }
1873
+ const controller = new AbortController();
1874
+ setArchiveSearch({ query: normalizedArchiveQuery, status: "loading", items: [], hasMore: false });
1875
+ const timer = window.setTimeout(() => {
1876
+ searchArchivedSessions(normalizedArchiveQuery, controller.signal).then((result) => {
1877
+ if (!controller.signal.aborted) setArchiveSearch({ query: normalizedArchiveQuery, status: "ready", items: result.items, hasMore: result.hasMore });
1878
+ }).catch(() => {
1879
+ if (!controller.signal.aborted) setArchiveSearch({ query: normalizedArchiveQuery, status: "error", items: [], hasMore: false });
1880
+ });
1881
+ }, 250);
1882
+ return () => { window.clearTimeout(timer); controller.abort(); };
1883
+ }, [archiveManagerOpen, normalizedArchiveQuery, searchArchivedSessions]);
1884
+ const archiveWorkspaceBySession = (0, react.useMemo)(() => {
1885
+ const result = /* @__PURE__ */ new Map();
1886
+ for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!result.has(sessionId)) result.set(sessionId, workspace.title);
1887
+ return result;
1888
+ }, [workspaces]);
1889
+ const archiveSnippets = (0, react.useMemo)(() => new Map(archiveSearch.items.map((item) => [item.sessionId, item.snippet])), [archiveSearch.items]);
1890
+ const archiveRows = (0, react.useMemo)(() => {
1891
+ const query = normalizedArchiveQuery.toLowerCase();
1892
+ const remoteIds = new Set(archiveSearch.items.map((item) => item.sessionId));
1893
+ const rows = archivedSessionIds.map((sessionId) => {
1894
+ const summary = archiveSessionList.byId[sessionId];
1895
+ return {
1896
+ id: sessionId,
1897
+ title: summary === void 0 ? sessionId : sessionTitle(summary),
1898
+ workspace: archiveWorkspaceBySession.get(sessionId) ?? t("group.ungrouped"),
1899
+ updatedAt: summary?.updatedAt ?? 0
1900
+ };
1901
+ });
1902
+ rows.sort((a, b) => b.updatedAt - a.updatedAt);
1903
+ if (query === "") return rows;
1904
+ return rows.filter((row) => row.title.toLowerCase().includes(query) || row.workspace.toLowerCase().includes(query) || remoteIds.has(row.id));
1905
+ }, [archiveSessionList, archivedSessionIds, archiveSearch.items, archiveWorkspaceBySession, normalizedArchiveQuery, t]);
1906
+ const onArchiveRestore = (sessionId) => {
1907
+ if (archiveBusyId !== null) return;
1908
+ setArchiveBusyId(sessionId);
1909
+ setArchiveError(null);
1910
+ restoreSession(sessionId).then(() => setArchiveBusyId(null)).catch((reason) => {
1911
+ setArchiveBusyId(null);
1912
+ setArchiveError(reason instanceof Error ? reason.message : String(reason));
1913
+ });
1914
+ };
1861
1915
  const [sessionDeleteTarget, setSessionDeleteTarget] = (0, react.useState)(null);
1862
1916
  const [sessionDeleting, setSessionDeleting] = (0, react.useState)(false);
1863
1917
  const [sessionDeleteError, setSessionDeleteError] = (0, react.useState)(null);
@@ -1982,7 +2036,19 @@ window.__ModuleLoader__.load({
1982
2036
  }),
1983
2037
  (0, react_jsx_runtime.jsxs)("div", {
1984
2038
  className: clsx(WorkspaceBrowser_module_css_default.headerActions, wide && searchExpanded && WorkspaceBrowser_module_css_default.headerActionsHidden),
1985
- children: [wide && (0, react_jsx_runtime.jsx)(ViewOptionsMenu, {
2039
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
2040
+ label: t("archive.manager.title"),
2041
+ side: "bottom",
2042
+ delayMs: 500,
2043
+ children: (0, react_jsx_runtime.jsx)("button", {
2044
+ id: "archived-sessions",
2045
+ type: "button",
2046
+ className: WorkspaceBrowser_module_css_default.iconButton,
2047
+ "aria-label": t("archive.manager.title"),
2048
+ onClick: () => { setArchiveError(null); setArchiveManagerOpen(true); },
2049
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: wide ? 16 : 18 })
2050
+ })
2051
+ }), wide && (0, react_jsx_runtime.jsx)(ViewOptionsMenu, {
1986
2052
  groupBy,
1987
2053
  orderBy,
1988
2054
  onGroupPick: (mode) => {
@@ -2210,6 +2276,82 @@ window.__ModuleLoader__.load({
2210
2276
  children: sessionRenameError
2211
2277
  })]
2212
2278
  }),
2279
+ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2280
+ open: archiveManagerOpen,
2281
+ onClose: () => { if (archiveBusyId === null) setArchiveManagerOpen(false); },
2282
+ closeLabel: t("close"),
2283
+ title: t("archive.manager.title"),
2284
+ description: t("archive.manager.description", { n: archivedSessionIds.length }),
2285
+ footer: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2286
+ variant: "outline",
2287
+ disabled: archiveBusyId !== null,
2288
+ onClick: () => setArchiveManagerOpen(false),
2289
+ children: t("close")
2290
+ }),
2291
+ children: [(0, react_jsx_runtime.jsx)("input", {
2292
+ className: WorkspaceBrowser_module_css_default.renameInput,
2293
+ type: "search",
2294
+ value: archiveQuery,
2295
+ maxLength: SEARCH_QUERY_MAX_CODE_UNITS,
2296
+ placeholder: t("archive.manager.searchPlaceholder"),
2297
+ "aria-label": t("archive.manager.searchPlaceholder"),
2298
+ onChange: (event) => { setArchiveQuery(event.target.value); setArchiveError(null); }
2299
+ }), archiveSearch.status === "loading" && (0, react_jsx_runtime.jsx)("div", {
2300
+ className: WorkspaceBrowser_module_css_default.deleteStatus,
2301
+ role: "status",
2302
+ style: { marginTop: 8 },
2303
+ children: t("archive.manager.searching")
2304
+ }), archiveSearch.status === "error" && (0, react_jsx_runtime.jsx)("div", {
2305
+ className: WorkspaceBrowser_module_css_default.renameError,
2306
+ role: "status",
2307
+ children: t("archive.manager.searchUnavailable")
2308
+ }), (0, react_jsx_runtime.jsx)("div", {
2309
+ style: { display: "flex", flexDirection: "column", gap: 8, maxHeight: "52vh", overflowY: "auto", marginTop: 12 },
2310
+ children: archiveRows.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
2311
+ className: WorkspaceBrowser_module_css_default.deleteStatus,
2312
+ children: normalizedArchiveQuery === "" ? t("archive.manager.empty") : t("archive.manager.noMatches")
2313
+ }) : archiveRows.map((row) => (0, react_jsx_runtime.jsxs)("div", {
2314
+ style: { border: "1px solid var(--dsw-alias-border-l2)", borderRadius: 12, padding: 12, display: "flex", flexDirection: "column", alignItems: "stretch", gap: 8 },
2315
+ children: [(0, react_jsx_runtime.jsxs)("div", {
2316
+ style: { minWidth: 0 },
2317
+ children: [(0, react_jsx_runtime.jsx)("div", {
2318
+ style: { color: "var(--dsw-alias-label-primary)", fontSize: 13, fontWeight: 500, whiteSpace: "normal", overflowWrap: "anywhere", lineHeight: "18px" },
2319
+ children: row.title
2320
+ }), (0, react_jsx_runtime.jsx)("div", {
2321
+ style: { color: "var(--dsw-alias-label-tertiary)", fontSize: 12, marginTop: 2 },
2322
+ children: row.workspace
2323
+ }), archiveSnippets.has(row.id) && (0, react_jsx_runtime.jsx)("div", {
2324
+ style: { color: "var(--dsw-alias-label-secondary)", fontSize: 12, lineHeight: "18px", marginTop: 6 },
2325
+ children: archiveSnippets.get(row.id)
2326
+ })]
2327
+ }), (0, react_jsx_runtime.jsxs)("div", {
2328
+ style: { display: "flex", justifyContent: "flex-end", gap: 8 },
2329
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2330
+ variant: "outline",
2331
+ style: { minHeight: 28, height: 28, paddingInline: 10, fontSize: 12 },
2332
+ disabled: archiveBusyId !== null,
2333
+ onClick: () => onArchiveRestore(row.id),
2334
+ children: archiveBusyId === row.id ? t("archive.manager.restoring") : t("archive.manager.restore")
2335
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2336
+ variant: "outline",
2337
+ className: WorkspaceBrowser_module_css_default.deleteAction,
2338
+ style: { minHeight: 28, height: 28, paddingInline: 10, fontSize: 12 },
2339
+ disabled: archiveBusyId !== null,
2340
+ onClick: () => onSessionDelete(row.id, row.title),
2341
+ children: t("archive.manager.delete")
2342
+ })]
2343
+ })]
2344
+ }, row.id))
2345
+ }), archiveSearch.hasMore && (0, react_jsx_runtime.jsx)("div", {
2346
+ className: WorkspaceBrowser_module_css_default.deleteStatus,
2347
+ style: { marginTop: 8 },
2348
+ children: t("archive.manager.hasMore")
2349
+ }), archiveError !== null && (0, react_jsx_runtime.jsx)("div", {
2350
+ className: WorkspaceBrowser_module_css_default.renameError,
2351
+ role: "alert",
2352
+ children: archiveError
2353
+ })]
2354
+ }),
2213
2355
  (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2214
2356
  open: sessionDeleteTarget !== null,
2215
2357
  onClose: closeSessionDelete,
@@ -2317,6 +2459,17 @@ window.__ModuleLoader__.load({
2317
2459
  "delete.pending": "正在删除工作区…",
2318
2460
  "menu.fork": "分叉会话",
2319
2461
  "menu.archiveSession": "归档会话",
2462
+ "archive.manager.title": "归档会话",
2463
+ "archive.manager.description": "共 {n} 个归档会话。可按名称、工作区或聊天内容搜索。",
2464
+ "archive.manager.searchPlaceholder": "搜索归档名称、工作区或聊天内容…",
2465
+ "archive.manager.searching": "正在搜索归档聊天记录…",
2466
+ "archive.manager.searchUnavailable": "内容搜索暂不可用,仅显示名称与工作区匹配。",
2467
+ "archive.manager.empty": "暂无归档会话",
2468
+ "archive.manager.noMatches": "没有匹配的归档会话",
2469
+ "archive.manager.hasMore": "仅显示前 20 条内容匹配,请缩小搜索范围。",
2470
+ "archive.manager.restore": "恢复",
2471
+ "archive.manager.restoring": "恢复中…",
2472
+ "archive.manager.delete": "永久删除",
2320
2473
  "menu.deleteSession": "删除会话",
2321
2474
  "delete.session.title": "永久删除会话?",
2322
2475
  "delete.session.desc": "“{name}”的会话记录将从本机永久删除,且无法恢复。正在运行的任务会先安全停止。",
@@ -2387,6 +2540,17 @@ window.__ModuleLoader__.load({
2387
2540
  "delete.pending": "Deleting workspace…",
2388
2541
  "menu.fork": "Fork session",
2389
2542
  "menu.archiveSession": "Archive session",
2543
+ "archive.manager.title": "Archived sessions",
2544
+ "archive.manager.description": "{n} archived sessions. Search by name, workspace, or conversation content.",
2545
+ "archive.manager.searchPlaceholder": "Search archived names, workspaces, or conversation content…",
2546
+ "archive.manager.searching": "Searching archived conversation history…",
2547
+ "archive.manager.searchUnavailable": "Content search is temporarily unavailable. Showing name and workspace matches.",
2548
+ "archive.manager.empty": "No archived sessions",
2549
+ "archive.manager.noMatches": "No matching archived sessions",
2550
+ "archive.manager.hasMore": "Showing the first 20 content matches. Narrow your search.",
2551
+ "archive.manager.restore": "Restore",
2552
+ "archive.manager.restoring": "Restoring…",
2553
+ "archive.manager.delete": "Delete permanently",
2390
2554
  "menu.deleteSession": "Delete session",
2391
2555
  "delete.session.title": "Permanently delete session?",
2392
2556
  "delete.session.desc": "The local record for “{name}” will be permanently deleted and cannot be recovered. Running work will be stopped safely before deletion.",
@@ -2515,6 +2679,31 @@ window.__ModuleLoader__.load({
2515
2679
  if (refresh.status === "rejected") console.warn("session deletion succeeded but runtime refresh failed:", refresh.reason);
2516
2680
  }
2517
2681
  },
2682
+ restoreSession: async (sessionId) => {
2683
+ const response = await fetch("/plugins/dsh-session-delete/restore", {
2684
+ method: "POST",
2685
+ headers: {
2686
+ "content-type": "application/json",
2687
+ "x-dsh-session-manager-action": "restore-session"
2688
+ },
2689
+ body: JSON.stringify({ sessionId })
2690
+ });
2691
+ const payload = await response.json().catch(() => null);
2692
+ if (!response.ok || payload?.ok !== true) throw new Error(payload?.error?.message ?? `Restore failed (HTTP ${response.status})`);
2693
+ const refreshes = await Promise.allSettled([ctx.sessions.refresh(), ctx.workspaces.refresh()]);
2694
+ for (const refresh of refreshes) if (refresh.status === "rejected") console.warn("session restore succeeded but runtime refresh failed:", refresh.reason);
2695
+ },
2696
+ searchArchivedSessions: async (query, signal) => {
2697
+ const response = await fetch("/plugins/dsh-session-delete/archive-search", {
2698
+ method: "POST",
2699
+ headers: { "content-type": "application/json" },
2700
+ body: JSON.stringify({ query }),
2701
+ signal
2702
+ });
2703
+ const payload = await response.json().catch(() => null);
2704
+ if (!response.ok || payload?.ok !== true) throw new Error(payload?.error?.message ?? `Archived search failed (HTTP ${response.status})`);
2705
+ return payload.value;
2706
+ },
2518
2707
  insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
2519
2708
  await ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
2520
2709
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-native-session-delete",
3
- "version": "1.0.7",
4
- "description": "Permanent session deletion in the native DeepSeek Harness menu",
3
+ "version": "1.1.1",
4
+ "description": "DeepSeek Harness chat history and session management: search archives, restore conversations, and delete safely",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "exports": {
@@ -16,9 +16,10 @@
16
16
  "scripts/build-client.mjs",
17
17
  "scripts/smoke-ui.mjs",
18
18
  "src/index.js",
19
+ "src/host/archive-manager.mjs",
19
20
  "src/host/delete-session.mjs",
20
21
  "README.md",
21
- "README.en.md",
22
+ "README.zh-CN.md",
22
23
  "AGENTS.md",
23
24
  "LICENSE",
24
25
  "SECURITY.md",
@@ -43,6 +44,19 @@
43
44
  "dsh-plugin",
44
45
  "deepseek-harness",
45
46
  "session",
47
+ "session-manager",
48
+ "session-management",
49
+ "conversation",
50
+ "archive",
51
+ "unarchive",
52
+ "restore",
53
+ "history-search",
54
+ "chat-history",
55
+ "conversation-history",
56
+ "archived-sessions",
57
+ "session-delete",
58
+ "permanent-delete",
59
+ "deepseek",
46
60
  "delete",
47
61
  "privacy"
48
62
  ],
@@ -53,12 +67,12 @@
53
67
  },
54
68
  "repository": {
55
69
  "type": "git",
56
- "url": "git+https://github.com/WSL043/dsh-native-session-delete.git"
70
+ "url": "git+https://github.com/WSL043/dsh-native-session-manager.git"
57
71
  },
58
72
  "bugs": {
59
- "url": "https://github.com/WSL043/dsh-native-session-delete/issues"
73
+ "url": "https://github.com/WSL043/dsh-native-session-manager/issues"
60
74
  },
61
- "homepage": "https://github.com/WSL043/dsh-native-session-delete#readme",
75
+ "homepage": "https://github.com/WSL043/dsh-native-session-manager#readme",
62
76
  "engines": {
63
77
  "node": "^22.19.0 || >=24.0.0"
64
78
  },
@@ -99,7 +99,7 @@ export function patchWorkspaceClient(upstream, upstreamVersion = LATEST_UPSTREAM
99
99
  workspaceBrowserSignature,
100
100
  workspaceBrowserSignature.replace(
101
101
  'deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore,',
102
- 'deleteWorkspace, insertWorkspaceBefore, archiveSession, deleteSession, insertSessionBefore,',
102
+ 'deleteWorkspace, insertWorkspaceBefore, archiveSession, deleteSession, restoreSession, searchArchivedSessions, insertSessionBefore,',
103
103
  ),
104
104
  'workspace browser delete action prop',
105
105
  )
@@ -108,6 +108,65 @@ export function patchWorkspaceClient(upstream, upstreamVersion = LATEST_UPSTREAM
108
108
  `\t\t\tconst onSessionArchive = (sessionId) => {\n\t\t\t\tarchiveSession(sessionId).catch((reason) => {\n\t\t\t\t\tconsole.warn("session archive rejected:", reason);\n\t\t\t\t});\n\t\t\t};\n\t\t\tconst [sessionDeleteTarget, setSessionDeleteTarget] = (0, react.useState)(null);\n\t\t\tconst [sessionDeleting, setSessionDeleting] = (0, react.useState)(false);\n\t\t\tconst [sessionDeleteError, setSessionDeleteError] = (0, react.useState)(null);\n\t\t\tconst onSessionDelete = (sessionId, title) => {\n\t\t\t\tsetSessionDeleteTarget({ sessionId, title });\n\t\t\t\tsetSessionDeleteError(null);\n\t\t\t};\n\t\t\tconst closeSessionDelete = () => {\n\t\t\t\tif (sessionDeleting) return;\n\t\t\t\tsetSessionDeleteTarget(null);\n\t\t\t\tsetSessionDeleteError(null);\n\t\t\t};\n\t\t\tconst confirmSessionDelete = () => {\n\t\t\t\tif (sessionDeleting || sessionDeleteTarget === null) return;\n\t\t\t\tsetSessionDeleting(true);\n\t\t\t\tsetSessionDeleteError(null);\n\t\t\t\tdeleteSession(sessionDeleteTarget.sessionId).then(() => {\n\t\t\t\t\tsetSessionDeleting(false);\n\t\t\t\t\tsetSessionDeleteTarget(null);\n\t\t\t\t\tsetSessionDeleteError(null);\n\t\t\t\t}).catch((reason) => {\n\t\t\t\t\tsetSessionDeleting(false);\n\t\t\t\t\tsetSessionDeleteError(reason instanceof Error ? reason.message : String(reason));\n\t\t\t\t});\n\t\t\t};\n`,
109
109
  'session delete dialog state',
110
110
  )
111
+ patch(
112
+ 'const [sessionDeleteTarget, setSessionDeleteTarget] = (0, react.useState)(null);',
113
+ `const archiveSessionList = useSessions((state) => state);
114
+ \t\t\tconst [archiveManagerOpen, setArchiveManagerOpen] = (0, react.useState)(false);
115
+ \t\t\tconst [archiveQuery, setArchiveQuery] = (0, react.useState)("");
116
+ \t\t\tconst [archiveSearch, setArchiveSearch] = (0, react.useState)({ query: "", status: "idle", items: [], hasMore: false });
117
+ \t\t\tconst [archiveBusyId, setArchiveBusyId] = (0, react.useState)(null);
118
+ \t\t\tconst [archiveError, setArchiveError] = (0, react.useState)(null);
119
+ \t\t\tconst normalizedArchiveQuery = archiveQuery.trim();
120
+ \t\t\t(0, react.useEffect)(() => {
121
+ \t\t\t\tif (!archiveManagerOpen || normalizedArchiveQuery === "") {
122
+ \t\t\t\t\tsetArchiveSearch({ query: "", status: "idle", items: [], hasMore: false });
123
+ \t\t\t\t\treturn;
124
+ \t\t\t\t}
125
+ \t\t\t\tconst controller = new AbortController();
126
+ \t\t\t\tsetArchiveSearch({ query: normalizedArchiveQuery, status: "loading", items: [], hasMore: false });
127
+ \t\t\t\tconst timer = window.setTimeout(() => {
128
+ \t\t\t\t\tsearchArchivedSessions(normalizedArchiveQuery, controller.signal).then((result) => {
129
+ \t\t\t\t\t\tif (!controller.signal.aborted) setArchiveSearch({ query: normalizedArchiveQuery, status: "ready", items: result.items, hasMore: result.hasMore });
130
+ \t\t\t\t\t}).catch(() => {
131
+ \t\t\t\t\t\tif (!controller.signal.aborted) setArchiveSearch({ query: normalizedArchiveQuery, status: "error", items: [], hasMore: false });
132
+ \t\t\t\t\t});
133
+ \t\t\t\t}, 250);
134
+ \t\t\t\treturn () => { window.clearTimeout(timer); controller.abort(); };
135
+ \t\t\t}, [archiveManagerOpen, normalizedArchiveQuery, searchArchivedSessions]);
136
+ \t\t\tconst archiveWorkspaceBySession = (0, react.useMemo)(() => {
137
+ \t\t\t\tconst result = /* @__PURE__ */ new Map();
138
+ \t\t\t\tfor (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!result.has(sessionId)) result.set(sessionId, workspace.title);
139
+ \t\t\t\treturn result;
140
+ \t\t\t}, [workspaces]);
141
+ \t\t\tconst archiveSnippets = (0, react.useMemo)(() => new Map(archiveSearch.items.map((item) => [item.sessionId, item.snippet])), [archiveSearch.items]);
142
+ \t\t\tconst archiveRows = (0, react.useMemo)(() => {
143
+ \t\t\t\tconst query = normalizedArchiveQuery.toLowerCase();
144
+ \t\t\t\tconst remoteIds = new Set(archiveSearch.items.map((item) => item.sessionId));
145
+ \t\t\t\tconst rows = archivedSessionIds.map((sessionId) => {
146
+ \t\t\t\t\tconst summary = archiveSessionList.byId[sessionId];
147
+ \t\t\t\t\treturn {
148
+ \t\t\t\t\t\tid: sessionId,
149
+ \t\t\t\t\t\ttitle: summary === void 0 ? sessionId : sessionTitle(summary),
150
+ \t\t\t\t\t\tworkspace: archiveWorkspaceBySession.get(sessionId) ?? t("group.ungrouped"),
151
+ \t\t\t\t\t\tupdatedAt: summary?.updatedAt ?? 0
152
+ \t\t\t\t\t};
153
+ \t\t\t\t});
154
+ \t\t\t\trows.sort((a, b) => b.updatedAt - a.updatedAt);
155
+ \t\t\t\tif (query === "") return rows;
156
+ \t\t\t\treturn rows.filter((row) => row.title.toLowerCase().includes(query) || row.workspace.toLowerCase().includes(query) || remoteIds.has(row.id));
157
+ \t\t\t}, [archiveSessionList, archivedSessionIds, archiveSearch.items, archiveWorkspaceBySession, normalizedArchiveQuery, t]);
158
+ \t\t\tconst onArchiveRestore = (sessionId) => {
159
+ \t\t\t\tif (archiveBusyId !== null) return;
160
+ \t\t\t\tsetArchiveBusyId(sessionId);
161
+ \t\t\t\tsetArchiveError(null);
162
+ \t\t\t\trestoreSession(sessionId).then(() => setArchiveBusyId(null)).catch((reason) => {
163
+ \t\t\t\t\tsetArchiveBusyId(null);
164
+ \t\t\t\t\tsetArchiveError(reason instanceof Error ? reason.message : String(reason));
165
+ \t\t\t\t});
166
+ \t\t\t};
167
+ \t\t\tconst [sessionDeleteTarget, setSessionDeleteTarget] = (0, react.useState)(null);`,
168
+ 'archive manager state',
169
+ )
111
170
  patch(
112
171
  '\t\t\t\t\t\t\tonSessionArchive,\n\t\t\t\t\t\t\tarchivedSessionIds,\n',
113
172
  '\t\t\t\t\t\t\tonSessionArchive,\n\t\t\t\t\t\t\tonSessionDelete,\n\t\t\t\t\t\t\tarchivedSessionIds,\n',
@@ -118,6 +177,106 @@ export function patchWorkspaceClient(upstream, upstreamVersion = LATEST_UPSTREAM
118
177
  '\t\t\t\t\t\t\tonSessionArchive,\n\t\t\t\t\t\t\tonSessionDelete,\n\t\t\t\t\t\t\tforkSession,\n',
119
178
  'session tree delete handler',
120
179
  )
180
+ patch(
181
+ 'children: [wide && (0, react_jsx_runtime.jsx)(ViewOptionsMenu, {',
182
+ `children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
183
+ \t\t\t\t\t\t\t\t\tlabel: t("archive.manager.title"),
184
+ \t\t\t\t\t\t\t\t\tside: "bottom",
185
+ \t\t\t\t\t\t\t\t\tdelayMs: 500,
186
+ \t\t\t\t\t\t\t\t\tchildren: (0, react_jsx_runtime.jsx)("button", {
187
+ \t\t\t\t\t\t\t\t\t\tid: "archived-sessions",
188
+ \t\t\t\t\t\t\t\t\t\ttype: "button",
189
+ \t\t\t\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.iconButton,
190
+ \t\t\t\t\t\t\t\t\t\t"aria-label": t("archive.manager.title"),
191
+ \t\t\t\t\t\t\t\t\t\tonClick: () => { setArchiveError(null); setArchiveManagerOpen(true); },
192
+ \t\t\t\t\t\t\t\t\t\tchildren: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: wide ? 16 : 18 })
193
+ \t\t\t\t\t\t\t\t\t})
194
+ \t\t\t\t\t\t\t\t}), wide && (0, react_jsx_runtime.jsx)(ViewOptionsMenu, {`,
195
+ 'archive manager header action',
196
+ )
197
+ patch(
198
+ `\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: deleteTarget !== null,\n`,
199
+ `\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
200
+ \t\t\t\t\t\topen: archiveManagerOpen,
201
+ \t\t\t\t\t\tonClose: () => { if (archiveBusyId === null) setArchiveManagerOpen(false); },
202
+ \t\t\t\t\t\tcloseLabel: t("close"),
203
+ \t\t\t\t\t\ttitle: t("archive.manager.title"),
204
+ \t\t\t\t\t\tdescription: t("archive.manager.description", { n: archivedSessionIds.length }),
205
+ \t\t\t\t\t\tfooter: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
206
+ \t\t\t\t\t\t\tvariant: "outline",
207
+ \t\t\t\t\t\t\tdisabled: archiveBusyId !== null,
208
+ \t\t\t\t\t\t\tonClick: () => setArchiveManagerOpen(false),
209
+ \t\t\t\t\t\t\tchildren: t("close")
210
+ \t\t\t\t\t\t}),
211
+ \t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)("input", {
212
+ \t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.renameInput,
213
+ \t\t\t\t\t\t\ttype: "search",
214
+ \t\t\t\t\t\t\tvalue: archiveQuery,
215
+ \t\t\t\t\t\t\tmaxLength: SEARCH_QUERY_MAX_CODE_UNITS,
216
+ \t\t\t\t\t\t\tplaceholder: t("archive.manager.searchPlaceholder"),
217
+ \t\t\t\t\t\t\t"aria-label": t("archive.manager.searchPlaceholder"),
218
+ \t\t\t\t\t\t\tonChange: (event) => { setArchiveQuery(event.target.value); setArchiveError(null); }
219
+ \t\t\t\t\t\t}), archiveSearch.status === "loading" && (0, react_jsx_runtime.jsx)("div", {
220
+ \t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.deleteStatus,
221
+ \t\t\t\t\t\t\trole: "status",
222
+ \t\t\t\t\t\t\tstyle: { marginTop: 8 },
223
+ \t\t\t\t\t\t\tchildren: t("archive.manager.searching")
224
+ \t\t\t\t\t\t}), archiveSearch.status === "error" && (0, react_jsx_runtime.jsx)("div", {
225
+ \t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.renameError,
226
+ \t\t\t\t\t\t\trole: "status",
227
+ \t\t\t\t\t\t\tchildren: t("archive.manager.searchUnavailable")
228
+ \t\t\t\t\t\t}), (0, react_jsx_runtime.jsx)("div", {
229
+ \t\t\t\t\t\t\tstyle: { display: "flex", flexDirection: "column", gap: 8, maxHeight: "52vh", overflowY: "auto", marginTop: 12 },
230
+ \t\t\t\t\t\t\tchildren: archiveRows.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
231
+ \t\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.deleteStatus,
232
+ \t\t\t\t\t\t\t\tchildren: normalizedArchiveQuery === "" ? t("archive.manager.empty") : t("archive.manager.noMatches")
233
+ \t\t\t\t\t\t\t}) : archiveRows.map((row) => (0, react_jsx_runtime.jsxs)("div", {
234
+ \t\t\t\t\t\t\t\tstyle: { border: "1px solid var(--dsw-alias-border-l2)", borderRadius: 12, padding: 12, display: "flex", flexDirection: "column", alignItems: "stretch", gap: 8 },
235
+ \t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsxs)("div", {
236
+ \t\t\t\t\t\t\t\t\tstyle: { minWidth: 0 },
237
+ \t\t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)("div", {
238
+ \t\t\t\t\t\t\t\t\t\tstyle: { color: "var(--dsw-alias-label-primary)", fontSize: 13, fontWeight: 500, whiteSpace: "normal", overflowWrap: "anywhere", lineHeight: "18px" },
239
+ \t\t\t\t\t\t\t\t\t\tchildren: row.title
240
+ \t\t\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsx)("div", {
241
+ \t\t\t\t\t\t\t\t\t\tstyle: { color: "var(--dsw-alias-label-tertiary)", fontSize: 12, marginTop: 2 },
242
+ \t\t\t\t\t\t\t\t\t\tchildren: row.workspace
243
+ \t\t\t\t\t\t\t\t\t}), archiveSnippets.has(row.id) && (0, react_jsx_runtime.jsx)("div", {
244
+ \t\t\t\t\t\t\t\t\t\tstyle: { color: "var(--dsw-alias-label-secondary)", fontSize: 12, lineHeight: "18px", marginTop: 6 },
245
+ \t\t\t\t\t\t\t\t\t\tchildren: archiveSnippets.get(row.id)
246
+ \t\t\t\t\t\t\t\t\t})]
247
+ \t\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsxs)("div", {
248
+ \t\t\t\t\t\t\t\t\tstyle: { display: "flex", justifyContent: "flex-end", gap: 8 },
249
+ \t\t\t\t\t\t\t\t\tchildren: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
250
+ \t\t\t\t\t\t\t\t\tvariant: "outline",
251
+ \t\t\t\t\t\t\t\t\tstyle: { minHeight: 28, height: 28, paddingInline: 10, fontSize: 12 },
252
+ \t\t\t\t\t\t\t\t\tdisabled: archiveBusyId !== null,
253
+ \t\t\t\t\t\t\t\t\tonClick: () => onArchiveRestore(row.id),
254
+ \t\t\t\t\t\t\t\t\tchildren: archiveBusyId === row.id ? t("archive.manager.restoring") : t("archive.manager.restore")
255
+ \t\t\t\t\t\t\t\t}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
256
+ \t\t\t\t\t\t\t\t\tvariant: "outline",
257
+ \t\t\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.deleteAction,
258
+ \t\t\t\t\t\t\t\t\tstyle: { minHeight: 28, height: 28, paddingInline: 10, fontSize: 12 },
259
+ \t\t\t\t\t\t\t\t\tdisabled: archiveBusyId !== null,
260
+ \t\t\t\t\t\t\t\t\tonClick: () => onSessionDelete(row.id, row.title),
261
+ \t\t\t\t\t\t\t\t\tchildren: t("archive.manager.delete")
262
+ \t\t\t\t\t\t\t\t})]
263
+ \t\t\t\t\t\t\t\t})]
264
+ \t\t\t\t\t\t\t}, row.id))
265
+ \t\t\t\t\t\t}), archiveSearch.hasMore && (0, react_jsx_runtime.jsx)("div", {
266
+ \t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.deleteStatus,
267
+ \t\t\t\t\t\t\tstyle: { marginTop: 8 },
268
+ \t\t\t\t\t\t\tchildren: t("archive.manager.hasMore")
269
+ \t\t\t\t\t\t}), archiveError !== null && (0, react_jsx_runtime.jsx)("div", {
270
+ \t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.renameError,
271
+ \t\t\t\t\t\t\trole: "alert",
272
+ \t\t\t\t\t\t\tchildren: archiveError
273
+ \t\t\t\t\t\t})]
274
+ \t\t\t\t\t}),
275
+ \t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
276
+ \t\t\t\t\t\topen: deleteTarget !== null,
277
+ `,
278
+ 'archive manager modal',
279
+ )
121
280
  patch(
122
281
  `\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: deleteTarget !== null,\n`,
123
282
  `\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: sessionDeleteTarget !== null,\n\t\t\t\t\t\tonClose: closeSessionDelete,\n\t\t\t\t\t\tcloseLabel: t("close"),\n\t\t\t\t\t\ttitle: t("delete.session.title"),\n\t\t\t\t\t\t...sessionDeleteTarget === null ? {} : { description: t("delete.session.desc", { name: sessionDeleteTarget.title }) },\n\t\t\t\t\t\tfooter: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {\n\t\t\t\t\t\t\tvariant: "outline",\n\t\t\t\t\t\t\tdisabled: sessionDeleting,\n\t\t\t\t\t\t\tonClick: closeSessionDelete,\n\t\t\t\t\t\t\tchildren: t("cancel")\n\t\t\t\t\t\t}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {\n\t\t\t\t\t\t\tvariant: "outline",\n\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.deleteAction,\n\t\t\t\t\t\t\tdisabled: sessionDeleting,\n\t\t\t\t\t\t\tonClick: confirmSessionDelete,\n\t\t\t\t\t\t\tchildren: t("delete.session.confirm")\n\t\t\t\t\t\t})] }),\n\t\t\t\t\t\tchildren: [sessionDeleting && (0, react_jsx_runtime.jsx)("div", {\n\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.deleteStatus,\n\t\t\t\t\t\t\trole: "status",\n\t\t\t\t\t\t\tchildren: t("delete.session.pending")\n\t\t\t\t\t\t}), sessionDeleteError !== null && (0, react_jsx_runtime.jsx)("div", {\n\t\t\t\t\t\t\tclassName: WorkspaceBrowser_module_css_default.renameError,\n\t\t\t\t\t\t\trole: "alert",\n\t\t\t\t\t\t\tchildren: sessionDeleteError\n\t\t\t\t\t\t})]\n\t\t\t\t\t}),\n\t\t\t\t\t(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {\n\t\t\t\t\t\topen: deleteTarget !== null,\n`,
@@ -125,12 +284,12 @@ export function patchWorkspaceClient(upstream, upstreamVersion = LATEST_UPSTREAM
125
284
  )
126
285
  patch(
127
286
  '\t\t\t"menu.archiveSession": "归档会话",\n',
128
- '\t\t\t"menu.archiveSession": "归档会话",\n\t\t\t"menu.deleteSession": "删除会话",\n\t\t\t"delete.session.title": "永久删除会话?",\n\t\t\t"delete.session.desc": "“{name}”的会话记录将从本机永久删除,且无法恢复。正在运行的任务会先安全停止。",\n\t\t\t"delete.session.confirm": "永久删除",\n\t\t\t"delete.session.pending": "正在永久删除会话…",\n',
287
+ '\t\t\t"menu.archiveSession": "归档会话",\n\t\t\t"archive.manager.title": "归档会话",\n\t\t\t"archive.manager.description": "共 {n} 个归档会话。可按名称、工作区或聊天内容搜索。",\n\t\t\t"archive.manager.searchPlaceholder": "搜索归档名称、工作区或聊天内容…",\n\t\t\t"archive.manager.searching": "正在搜索归档聊天记录…",\n\t\t\t"archive.manager.searchUnavailable": "内容搜索暂不可用,仅显示名称与工作区匹配。",\n\t\t\t"archive.manager.empty": "暂无归档会话",\n\t\t\t"archive.manager.noMatches": "没有匹配的归档会话",\n\t\t\t"archive.manager.hasMore": "仅显示前 20 条内容匹配,请缩小搜索范围。",\n\t\t\t"archive.manager.restore": "恢复",\n\t\t\t"archive.manager.restoring": "恢复中…",\n\t\t\t"archive.manager.delete": "永久删除",\n\t\t\t"menu.deleteSession": "删除会话",\n\t\t\t"delete.session.title": "永久删除会话?",\n\t\t\t"delete.session.desc": "“{name}”的会话记录将从本机永久删除,且无法恢复。正在运行的任务会先安全停止。",\n\t\t\t"delete.session.confirm": "永久删除",\n\t\t\t"delete.session.pending": "正在永久删除会话…",\n',
129
288
  'Chinese delete locale',
130
289
  )
131
290
  patch(
132
291
  '\t\t\t"menu.archiveSession": "Archive session",\n',
133
- '\t\t\t"menu.archiveSession": "Archive session",\n\t\t\t"menu.deleteSession": "Delete session",\n\t\t\t"delete.session.title": "Permanently delete session?",\n\t\t\t"delete.session.desc": "The local record for “{name}” will be permanently deleted and cannot be recovered. Running work will be stopped safely before deletion.",\n\t\t\t"delete.session.confirm": "Delete permanently",\n\t\t\t"delete.session.pending": "Permanently deleting session…",\n',
292
+ '\t\t\t"menu.archiveSession": "Archive session",\n\t\t\t"archive.manager.title": "Archived sessions",\n\t\t\t"archive.manager.description": "{n} archived sessions. Search by name, workspace, or conversation content.",\n\t\t\t"archive.manager.searchPlaceholder": "Search archived names, workspaces, or conversation content…",\n\t\t\t"archive.manager.searching": "Searching archived conversation history…",\n\t\t\t"archive.manager.searchUnavailable": "Content search is temporarily unavailable. Showing name and workspace matches.",\n\t\t\t"archive.manager.empty": "No archived sessions",\n\t\t\t"archive.manager.noMatches": "No matching archived sessions",\n\t\t\t"archive.manager.hasMore": "Showing the first 20 content matches. Narrow your search.",\n\t\t\t"archive.manager.restore": "Restore",\n\t\t\t"archive.manager.restoring": "Restoring…",\n\t\t\t"archive.manager.delete": "Delete permanently",\n\t\t\t"menu.deleteSession": "Delete session",\n\t\t\t"delete.session.title": "Permanently delete session?",\n\t\t\t"delete.session.desc": "The local record for “{name}” will be permanently deleted and cannot be recovered. Running work will be stopped safely before deletion.",\n\t\t\t"delete.session.confirm": "Delete permanently",\n\t\t\t"delete.session.pending": "Permanently deleting session…",\n',
134
293
  'English delete locale',
135
294
  )
136
295
  patch(
@@ -138,6 +297,37 @@ export function patchWorkspaceClient(upstream, upstreamVersion = LATEST_UPSTREAM
138
297
  `\t\t\t\tarchiveSession: async (sessionId) => {\n\t\t\t\t\tawait ctx.workspaces.archiveSession(sessionId);\n\t\t\t\t},\n\t\t\t\tdeleteSession: async (sessionId) => {\n\t\t\t\t\tconst response = await fetch("/plugins/dsh-session-delete/delete", {\n\t\t\t\t\t\tmethod: "POST",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t"content-type": "application/json",\n\t\t\t\t\t\t\t"x-dsh-session-delete-confirmation": "delete-session"\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: JSON.stringify({ sessionId })\n\t\t\t\t\t});\n\t\t\t\t\tconst payload = await response.json().catch(() => null);\n\t\t\t\t\tif (!response.ok || payload?.ok !== true) {\n\t\t\t\t\t\tthrow new Error(payload?.error?.message ?? \`Delete failed (HTTP \${response.status})\`);\n\t\t\t\t\t}\n\t\t\t\t\tif (ctx.sessions.list.getSnapshot().current === sessionId) ctx.sessions.clear();\n\t\t\t\t\tconst refreshes = await Promise.allSettled([\n\t\t\t\t\t\tctx.sessions.refresh(),\n\t\t\t\t\t\tctx.workspaces.refresh()\n\t\t\t\t\t]);\n\t\t\t\t\tfor (const refresh of refreshes) {\n\t\t\t\t\t\tif (refresh.status === "rejected") console.warn("session deletion succeeded but runtime refresh failed:", refresh.reason);\n\t\t\t\t\t}\n\t\t\t\t},\n`,
139
298
  'browser delete request',
140
299
  )
300
+ patch(
301
+ '\t\t\t\tinsertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {\n',
302
+ `\t\t\t\trestoreSession: async (sessionId) => {
303
+ \t\t\t\t\tconst response = await fetch("/plugins/dsh-session-delete/restore", {
304
+ \t\t\t\t\t\tmethod: "POST",
305
+ \t\t\t\t\t\theaders: {
306
+ \t\t\t\t\t\t\t"content-type": "application/json",
307
+ \t\t\t\t\t\t\t"x-dsh-session-manager-action": "restore-session"
308
+ \t\t\t\t\t\t},
309
+ \t\t\t\t\t\tbody: JSON.stringify({ sessionId })
310
+ \t\t\t\t\t});
311
+ \t\t\t\t\tconst payload = await response.json().catch(() => null);
312
+ \t\t\t\t\tif (!response.ok || payload?.ok !== true) throw new Error(payload?.error?.message ?? \`Restore failed (HTTP \${response.status})\`);
313
+ \t\t\t\t\tconst refreshes = await Promise.allSettled([ctx.sessions.refresh(), ctx.workspaces.refresh()]);
314
+ \t\t\t\t\tfor (const refresh of refreshes) if (refresh.status === "rejected") console.warn("session restore succeeded but runtime refresh failed:", refresh.reason);
315
+ \t\t\t\t},
316
+ \t\t\t\tsearchArchivedSessions: async (query, signal) => {
317
+ \t\t\t\t\tconst response = await fetch("/plugins/dsh-session-delete/archive-search", {
318
+ \t\t\t\t\t\tmethod: "POST",
319
+ \t\t\t\t\t\theaders: { "content-type": "application/json" },
320
+ \t\t\t\t\t\tbody: JSON.stringify({ query }),
321
+ \t\t\t\t\t\tsignal
322
+ \t\t\t\t\t});
323
+ \t\t\t\t\tconst payload = await response.json().catch(() => null);
324
+ \t\t\t\t\tif (!response.ok || payload?.ok !== true) throw new Error(payload?.error?.message ?? \`Archived search failed (HTTP \${response.status})\`);
325
+ \t\t\t\t\treturn payload.value;
326
+ \t\t\t\t},
327
+ \t\t\t\tinsertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
328
+ `,
329
+ 'browser archive manager requests',
330
+ )
141
331
  const homePathCall = '(0, _deepseek_ai_dsh_client_runtime_client.abbreviateHomePath)(row.cwd, home)'
142
332
  if (source.includes(homePathCall)) {
143
333
  patch(
@@ -147,7 +337,7 @@ export function patchWorkspaceClient(upstream, upstreamVersion = LATEST_UPSTREAM
147
337
  )
148
338
  }
149
339
 
150
- const notice = `// Modified from @deepseek-ai/dsh-client-ui-workspace ${upstreamVersion} by DSH Native Session Delete. See THIRD_PARTY_NOTICES.md.\n`
340
+ const notice = `// Modified from @deepseek-ai/dsh-client-ui-workspace ${upstreamVersion} by DSH Native Session Manager. See THIRD_PARTY_NOTICES.md.\n`
151
341
  return `${notice}${source}`
152
342
  }
153
343
 
@@ -133,6 +133,11 @@ export async function runSmoke(options) {
133
133
  })
134
134
  }
135
135
  }
136
+ await page.locator('#archived-sessions').click()
137
+ const archiveDialog = page.getByRole('dialog', { name: /^(Archived sessions|归档会话)$/ })
138
+ await archiveDialog.getByRole('searchbox').waitFor()
139
+ await archiveDialog.getByRole('button', { name: /^(Close|关闭)$/ }).filter({ hasText: /^(Close|关闭)$/ }).click()
140
+ await archiveDialog.waitFor({ state: 'hidden' })
136
141
  const matchingTitles = page.getByText(options.session, { exact: true })
137
142
  await matchingTitles.first().waitFor()
138
143
  const rowCount = await matchingTitles.count()
@@ -193,8 +198,8 @@ export async function runSmoke(options) {
193
198
  return {
194
199
  ok: true,
195
200
  checks: options.simulateDeleteSuccess === true
196
- ? ['Archive session', 'red Delete session', 'confirmation dialog', 'successful delete without reload']
197
- : ['Archive session', 'red Delete session', 'confirmation dialog', 'cancel without request'],
201
+ ? ['archive manager', 'Archive session', 'red Delete session', 'confirmation dialog', 'successful delete without reload']
202
+ : ['archive manager', 'Archive session', 'red Delete session', 'confirmation dialog', 'cancel without request'],
198
203
  }
199
204
  } finally {
200
205
  await browser.close()