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/AGENTS.md +17 -12
- package/README.md +94 -61
- package/README.zh-CN.md +142 -0
- package/lib/client.js +192 -3
- package/package.json +20 -6
- package/scripts/build-client.mjs +194 -4
- package/scripts/smoke-ui.mjs +7 -2
- package/src/host/archive-manager.mjs +251 -0
- package/src/index.js +47 -7
- package/README.en.md +0 -134
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
|
|
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: [
|
|
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.
|
|
4
|
-
"description": "
|
|
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.
|
|
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-
|
|
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-
|
|
73
|
+
"url": "https://github.com/WSL043/dsh-native-session-manager/issues"
|
|
60
74
|
},
|
|
61
|
-
"homepage": "https://github.com/WSL043/dsh-native-session-
|
|
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
|
},
|
package/scripts/build-client.mjs
CHANGED
|
@@ -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
|
|
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
|
|
package/scripts/smoke-ui.mjs
CHANGED
|
@@ -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()
|