@michengai/dsh-archive-manager 0.1.1 → 0.1.3
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 +11 -17
- package/lib/projcache.js +24 -17
- package/lib/workspace.js +40 -62
- package/package.json +16 -2
package/lib/client.js
CHANGED
|
@@ -492,9 +492,9 @@ window.__ModuleLoader__.load({
|
|
|
492
492
|
* except workspace Rename/Delete and session Rename/Fork/Archive; the session
|
|
493
493
|
* and workspace hover cards are suppressed while a menu is open.
|
|
494
494
|
*/
|
|
495
|
-
/** Row display title: blank rows show the localized New Session label. */
|
|
496
|
-
function displayTitle(node, t) {
|
|
497
|
-
return node.blank ? t("session.new") : node.title;
|
|
495
|
+
/** Row display title: blank rows show the localized New Session label. */
|
|
496
|
+
function displayTitle(node, t) {
|
|
497
|
+
return node.blank ? t("session.new") : node.title ?? node.displayTitle ?? "";
|
|
498
498
|
}
|
|
499
499
|
/** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
|
|
500
500
|
function timeLabel(updatedAt, now, t) {
|
|
@@ -2456,7 +2456,7 @@ window.__ModuleLoader__.load({
|
|
|
2456
2456
|
//#endregion
|
|
2457
2457
|
//#region dsh-archive-manager: settings section
|
|
2458
2458
|
const ARCHIVE_SETTINGS_CSS = ".dsham_settings{box-sizing:border-box;width:min(100%,768px);margin:64px auto 80px;padding:0 20px;color:var(--dsw-alias-label-primary)}.dsham_settingsHeader{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:48px}.dsham_settings h2{margin:0;font-size:24px;font-weight:650;letter-spacing:-.02em;line-height:32px}.dsham_settingsDanger{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 11px;color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);border:0;border-radius:9px;cursor:pointer;font-size:12px;font-weight:600}.dsham_settingsDanger:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)}.dsham_settingsToolbar{display:flex;gap:8px;margin-bottom:40px}.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-button-elevated-fill);border:1px solid var(--dsw-alias-border-l2);border-radius:16px}.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{min-width:132px;height:30px;padding:0 28px 0 11px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-elevated-fill);border:1px solid var(--dsw-alias-border-l2);border-radius:9px;cursor:pointer;font:inherit;font-size:12px}.dsham_settingsGroup{margin:0 0 48px}.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:14px;background: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{height:30px;padding:0 10px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);border:0;border-radius:9px;cursor:pointer;font-size:12px;font-weight:600}.dsham_settingsAction:hover{filter:brightness(1.12)}.dsham_settingsDelete{display:flex;align-items:center;justify-content:center;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsDelete:hover{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsEmpty{padding:28px 8px;color:var(--dsw-alias-label-secondary);text-align:center}.dsham_settingsError{margin-top:10px;color:var(--dsw-alias-state-error-primary);font-size:12px}@media(max-width:720px){.dsham_settings{width:100%;margin:28px auto 48px;padding:0 16px}.dsham_settingsHeader{margin-bottom:28px}.dsham_settingsToolbar{flex-wrap:wrap;margin-bottom:28px}.dsham_settingsSearch{flex-basis:100%}.dsham_settingsFilter{flex:1;min-width:0}.dsham_settingsGroup{margin-bottom:32px}.dsham_settingsRow{padding:10px 12px}.dsham_settingsActions{gap:4px}}";
|
|
2459
|
-
const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:24px auto 80px!important}
|
|
2459
|
+
const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:24px auto 80px!important}@media(max-width:720px){.dsham_settings{margin:16px auto 48px!important}}";
|
|
2460
2460
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
|
|
2461
2461
|
const tag = document.createElement("style");
|
|
2462
2462
|
tag.dataset.plugin = "@michengai/dsh-archive-manager";
|
|
@@ -2472,7 +2472,6 @@ window.__ModuleLoader__.load({
|
|
|
2472
2472
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
2473
2473
|
const [error, setError] = (0, react.useState)(null);
|
|
2474
2474
|
const [query, setQuery] = (0, react.useState)("");
|
|
2475
|
-
const [chatType, setChatType] = (0, react.useState)("all");
|
|
2476
2475
|
const [project, setProject] = (0, react.useState)("all");
|
|
2477
2476
|
const groups = (0, react.useMemo)(() => {
|
|
2478
2477
|
const archived = new Set(workspaceState.archivedSessionIds);
|
|
@@ -2496,9 +2495,9 @@ window.__ModuleLoader__.load({
|
|
|
2496
2495
|
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
2497
2496
|
return groups.filter((group) => project === "all" || project === group.title).map((group) => ({
|
|
2498
2497
|
...group,
|
|
2499
|
-
sessions: group.sessions.filter((session) =>
|
|
2498
|
+
sessions: group.sessions.filter((session) => normalizedQuery === "" || displayTitle(session, t).toLocaleLowerCase().includes(normalizedQuery))
|
|
2500
2499
|
})).filter((group) => group.sessions.length > 0);
|
|
2501
|
-
}, [
|
|
2500
|
+
}, [groups, project, query, t]);
|
|
2502
2501
|
const archivedSessionIds = (0, react.useMemo)(() => groups.flatMap((group) => group.sessions.map((session) => session.id)), [groups]);
|
|
2503
2502
|
const onUnarchive = (sessionId) => {
|
|
2504
2503
|
setError(null);
|
|
@@ -2525,7 +2524,7 @@ window.__ModuleLoader__.load({
|
|
|
2525
2524
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
2526
2525
|
className: "dsham_settings",
|
|
2527
2526
|
"aria-label": t("archives.title"),
|
|
2528
|
-
children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS }), (0, react_jsx_runtime.jsxs)("header", { className: "dsham_settingsHeader", children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "dsham_settingsDanger", disabled: busy || archivedSessionIds.length === 0, onClick: () => setDeleteTarget({ all: true }), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}), t("archives.deleteAll")] })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsToolbar", children: [(0, react_jsx_runtime.jsxs)("label", { className: "dsham_settingsSearch", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })] }), (0, react_jsx_runtime.jsx)("select", { className: "dsham_settingsFilter", value:
|
|
2527
|
+
children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS }), (0, react_jsx_runtime.jsxs)("header", { className: "dsham_settingsHeader", children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "dsham_settingsDanger", disabled: busy || archivedSessionIds.length === 0, onClick: () => setDeleteTarget({ all: true }), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}), t("archives.deleteAll")] })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsToolbar", children: [(0, react_jsx_runtime.jsxs)("label", { className: "dsham_settingsSearch", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })] }), (0, react_jsx_runtime.jsx)("select", { className: "dsham_settingsFilter", value: project, onChange: (event) => setProject(event.target.value), "aria-label": t("archives.projectFilter"), children: [(0, react_jsx_runtime.jsx)("option", { value: "all", children: t("archives.allProjects") }), groups.map((group) => (0, react_jsx_runtime.jsx)("option", { value: group.title, children: group.title }, group.title))] })] }), 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) => (0, react_jsx_runtime.jsxs)("section", {
|
|
2529
2528
|
className: "dsham_settingsGroup",
|
|
2530
2529
|
children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsGroupHeading", 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.jsx)("span", { className: "dsham_settingsCount", children: t("archives.sessionCount", { n: group.sessions.length }) })] }), (0, react_jsx_runtime.jsx)("div", {
|
|
2531
2530
|
className: "dsham_settingsList",
|
|
@@ -2591,8 +2590,6 @@ window.__ModuleLoader__.load({
|
|
|
2591
2590
|
"archives.empty": "暂无已归档会话",
|
|
2592
2591
|
"archives.emptyFiltered": "没有匹配的已归档聊天",
|
|
2593
2592
|
"archives.searchPlaceholder": "搜索已归档聊天",
|
|
2594
|
-
"archives.chatFilter": "按聊天类型筛选",
|
|
2595
|
-
"archives.allChats": "全部聊天",
|
|
2596
2593
|
"archives.projectFilter": "按项目筛选",
|
|
2597
2594
|
"archives.allProjects": "所有项目",
|
|
2598
2595
|
"archives.sessionCount": "{n} 个聊天",
|
|
@@ -2678,8 +2675,6 @@ window.__ModuleLoader__.load({
|
|
|
2678
2675
|
"archives.empty": "No archived sessions.",
|
|
2679
2676
|
"archives.emptyFiltered": "No archived chats match your filters.",
|
|
2680
2677
|
"archives.searchPlaceholder": "Search archived chats",
|
|
2681
|
-
"archives.chatFilter": "Filter by chat type",
|
|
2682
|
-
"archives.allChats": "All chats",
|
|
2683
2678
|
"archives.projectFilter": "Filter by project",
|
|
2684
2679
|
"archives.allProjects": "All projects",
|
|
2685
2680
|
"archives.sessionCount": "{n} chats",
|
|
@@ -2885,8 +2880,9 @@ window.__ModuleLoader__.load({
|
|
|
2885
2880
|
}
|
|
2886
2881
|
//#endregion
|
|
2887
2882
|
/** Pure derivation surface for dsh-archive-manager self-tests (no-op for the runtime). */
|
|
2888
|
-
exports.__test = {
|
|
2889
|
-
|
|
2883
|
+
exports.__test = {
|
|
2884
|
+
displayTitle,
|
|
2885
|
+
sessionVisible,
|
|
2890
2886
|
deriveGroups,
|
|
2891
2887
|
deriveFlat,
|
|
2892
2888
|
deriveSearchResults,
|
|
@@ -2899,6 +2895,4 @@ window.__ModuleLoader__.load({
|
|
|
2899
2895
|
exports.inject = inject;
|
|
2900
2896
|
return module.exports;
|
|
2901
2897
|
}
|
|
2902
|
-
});
|
|
2903
|
-
|
|
2904
|
-
//# sourceMappingURL=client.js.map
|
|
2898
|
+
});
|
package/lib/projcache.js
CHANGED
|
@@ -10,29 +10,35 @@ import { SessionProjectionCache } from "@deepseek-ai/dsh-session-projection-cach
|
|
|
10
10
|
*
|
|
11
11
|
* - `delete(id)` — permanently remove one session's cached projection row
|
|
12
12
|
* (`table.delete` on the `session_projcache` domain).
|
|
13
|
-
* - `whenIdle()` — resolve once every in-flight
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* after deletion and resurrects the cache entry.
|
|
13
|
+
* - `whenIdle()` — resolve once every in-flight public `write()` has settled.
|
|
14
|
+
* - `delete(id)` installs a tombstone before removing the row, so an earlier
|
|
15
|
+
* write that settles late removes its own stale row instead of resurrecting
|
|
16
|
+
* the deleted cache entry.
|
|
18
17
|
*
|
|
19
18
|
* The default export is a Service subclass (same shape as the shipped
|
|
20
19
|
* `@deepseek-ai/dsh-session-projection-cache` package), so the profile patch
|
|
21
20
|
* can substitute this package for the `session-projection-cache` row with no
|
|
22
21
|
* other wiring change.
|
|
23
22
|
*/
|
|
24
|
-
var ArchiveProjectionCache = class extends SessionProjectionCache {
|
|
25
|
-
/**
|
|
26
|
-
|
|
23
|
+
var ArchiveProjectionCache = class extends SessionProjectionCache {
|
|
24
|
+
/** 已永久删除的会话不会再允许投影缓存写入。 */
|
|
25
|
+
deletedSessionIds = /* @__PURE__ */ new Set();
|
|
26
|
+
/** Tail of in-flight checkpoint writes (settled promises only). */
|
|
27
|
+
writeTail = Promise.resolve();
|
|
27
28
|
constructor(ctx, config) {
|
|
28
29
|
super(ctx, config);
|
|
29
30
|
}
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
const task =
|
|
33
|
-
this.writeTail = Promise.allSettled([this.writeTail, task]).then(() => void 0);
|
|
34
|
-
return task;
|
|
35
|
-
}
|
|
31
|
+
/** 跟踪公开写入路径,避免依赖上游私有 `flushSoft` 的实现细节。 */
|
|
32
|
+
write(session) {
|
|
33
|
+
const task = this.writeCore(session);
|
|
34
|
+
this.writeTail = Promise.allSettled([this.writeTail, task]).then(() => void 0);
|
|
35
|
+
return task;
|
|
36
|
+
}
|
|
37
|
+
async writeCore(session) {
|
|
38
|
+
if (this.deletedSessionIds.has(session.id)) return;
|
|
39
|
+
await super.write(session);
|
|
40
|
+
if (this.deletedSessionIds.has(session.id)) await this.requireTable().delete(session.id);
|
|
41
|
+
}
|
|
36
42
|
/**
|
|
37
43
|
* Resolve once every tracked in-flight write has settled (failures
|
|
38
44
|
* included). Fresh calls during an idle cache resolve immediately.
|
|
@@ -46,9 +52,10 @@ var ArchiveProjectionCache = class extends SessionProjectionCache {
|
|
|
46
52
|
* @param id - the session whose cache row to delete.
|
|
47
53
|
* @returns resolution after the row is gone.
|
|
48
54
|
*/
|
|
49
|
-
async delete(id) {
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
async delete(id) {
|
|
56
|
+
this.deletedSessionIds.add(id);
|
|
57
|
+
await this.requireTable().delete(id);
|
|
58
|
+
}
|
|
52
59
|
};
|
|
53
60
|
//#endregion
|
|
54
61
|
export { ArchiveProjectionCache, ArchiveProjectionCache as default };
|
package/lib/workspace.js
CHANGED
|
@@ -7,51 +7,18 @@ import {
|
|
|
7
7
|
import { bindTypertRemote, Remote } from "@deepseek-ai/dsh-typert-protocol";
|
|
8
8
|
import { sessionDir } from "@deepseek-ai/dsh-spill-local";
|
|
9
9
|
//#region lib/types/index.js
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* - `deleteSession(sessionId)` — PERMANENT deletion, no trace left on disk:
|
|
23
|
-
* 1. validate the session is known (live, header-indexed, or persisted);
|
|
24
|
-
* 2. if live: flush buffered events to disk, then detach the session from
|
|
25
|
-
* the sessions store (`session/disposed` is emitted, so the browser
|
|
26
|
-
* receives `host/session-removed` and greys the composer);
|
|
27
|
-
* 3. wait for the projection cache's dispose write-behind (`whenIdle`) so
|
|
28
|
-
* the final cache row is durable BEFORE it is deleted;
|
|
29
|
-
* 4. remove the transcript directory (the whole `session-<id>` dir under
|
|
30
|
-
* the persistence root, located through `sessionPersistence.locate`);
|
|
31
|
-
* 5. remove the id from `archivedSessionIds` (durable `setState`);
|
|
32
|
-
* 6. remove the id from every workspace record's `sessionIds` and refresh
|
|
33
|
-
* the in-memory entity snapshots (durable `table.update`, so the host
|
|
34
|
-
* stream pushes `host/workspace-changed` to every browser);
|
|
35
|
-
* 7. delete the projection-cache row (`sessionProjectionCache.delete`);
|
|
36
|
-
* 8. best-effort (failures only warn): cascade-delete SUBAGENT child
|
|
37
|
-
* sessions of the deleted session (`header.origin === "subagent"` ONLY —
|
|
38
|
-
* a fork branch carries `parentSession` too but is an independent user
|
|
39
|
-
* session and is never cascade-deleted) and clear its spill data.
|
|
40
|
-
*
|
|
41
|
-
* Core steps throw on failure (no half-delete states: every failing step is
|
|
42
|
-
* idempotent and re-runnable, so a retry heals); cascade/spill are
|
|
43
|
-
* best-effort by design.
|
|
44
|
-
*
|
|
45
|
-
* The two new methods are also exported as Typert Remote endpoints
|
|
46
|
-
* (`workspaceRegistry/unarchiveSession`, `workspaceRegistry/deleteSession`)
|
|
47
|
-
* through the service's `typertRemote` binding + `Remote` markers — the
|
|
48
|
-
* browser reaches them through the standard typert gateway SRC path, keeping
|
|
49
|
-
* the legacy `/api/workspace.*` gateway untouched.
|
|
50
|
-
*
|
|
51
|
-
* The default export is a Service subclass (same shape as the shipped
|
|
52
|
-
* `@deepseek-ai/dsh-workspace` package), so the profile patch can substitute
|
|
53
|
-
* this package for the `workspace` row with no other wiring change.
|
|
54
|
-
*/
|
|
10
|
+
/**
|
|
11
|
+
* dsh-archive-manager 宿主侧归档会话管理。
|
|
12
|
+
*
|
|
13
|
+
* `deleteSession(sessionId)` 的顺序即语义:校验已知会话;实时会话先
|
|
14
|
+
* flush 再 detach;等待投影缓存写入完成;移除归档标记和工作区记账;
|
|
15
|
+
* 删除投影缓存,级联删除 SUBAGENT 子会话并清理 spill;最后才删除转录
|
|
16
|
+
* 目录。fork 分支虽有 `parentSession`,但属于独立用户会话,不参与级联。
|
|
17
|
+
*
|
|
18
|
+
* 所有可失败的持久化清理都先于不可逆的物理删除。此前任一步失败时,
|
|
19
|
+
* 转录仍保留,可重试删除;物理删除失败时记录残留路径供人工排查。
|
|
20
|
+
* `unarchiveSession` 与 `deleteSession` 通过 Typert Remote 暴露给浏览器。
|
|
21
|
+
*/
|
|
55
22
|
function markRemoteMethod(instance, method) {
|
|
56
23
|
// Simulate the TS decorator pipeline `@Remote(method)` for one method:
|
|
57
24
|
// `Remote` returns a standard method decorator; we hand it a decorator
|
|
@@ -128,21 +95,22 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
128
95
|
sessions.detachEntered(entry);
|
|
129
96
|
} else if (sessions !== void 0) await this.publishColdSessionRemoval(sessionId, sessions);
|
|
130
97
|
const projCache = this.ctx.get("sessionProjectionCache");
|
|
131
|
-
// The dispose write-behind must land BEFORE the cache row is deleted,
|
|
132
|
-
// otherwise the row is written back after deletion (resurrected).
|
|
133
|
-
await projCache?.whenIdle?.();
|
|
134
|
-
|
|
135
|
-
const state = this.requireState();
|
|
98
|
+
// The dispose write-behind must land BEFORE the cache row is deleted,
|
|
99
|
+
// otherwise the row is written back after deletion (resurrected).
|
|
100
|
+
await projCache?.whenIdle?.();
|
|
101
|
+
// 物理删除不可逆:先完成所有可失败的持久化清理,失败时仍可重试。
|
|
102
|
+
const state = this.requireState();
|
|
136
103
|
if (state.archivedSessionIds.includes(sessionId)) {
|
|
137
104
|
await this.setState({
|
|
138
105
|
...state,
|
|
139
106
|
archivedSessionIds: state.archivedSessionIds.filter((id) => id !== sessionId)
|
|
140
107
|
});
|
|
141
|
-
}
|
|
142
|
-
await this.removeFromWorkspaceAccounts(sessionId);
|
|
143
|
-
if (projCache !== void 0) await projCache.delete(sessionId);
|
|
144
|
-
await this.deleteDescendants(sessionId);
|
|
145
|
-
await this.cleanSpill(sessionId);
|
|
108
|
+
}
|
|
109
|
+
await this.removeFromWorkspaceAccounts(sessionId);
|
|
110
|
+
if (projCache !== void 0) await projCache.delete(sessionId);
|
|
111
|
+
await this.deleteDescendants(sessionId);
|
|
112
|
+
await this.cleanSpill(sessionId);
|
|
113
|
+
await this.removeTranscriptDirectory(sessionId);
|
|
146
114
|
return { deleted: true };
|
|
147
115
|
}
|
|
148
116
|
/** 为未处于实时状态的持久化会话发布相同的移除事件。 */
|
|
@@ -162,17 +130,27 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
162
130
|
this.ctx.logger.warn(`archive-manager: could not publish removal for stored session "${sessionId}": ${String(error)}`);
|
|
163
131
|
}
|
|
164
132
|
}
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
async removeTranscriptDirectory(sessionId) {
|
|
133
|
+
/** 删除会话的转录目录;仅在所有记账清理完成后调用。 */
|
|
134
|
+
async removeTranscriptDirectory(sessionId) {
|
|
168
135
|
const persistence = this.ctx.get("sessionPersistence");
|
|
169
136
|
if (persistence === void 0 || typeof persistence.locate !== "function") {
|
|
170
137
|
throw new Error(`cannot delete session "${sessionId}": the session persistence backend does not expose locate() to resolve its transcript directory`);
|
|
171
138
|
}
|
|
172
|
-
const header = await this.readSessionHeader(sessionId);
|
|
173
|
-
const location = persistence.locate(header);
|
|
174
|
-
|
|
175
|
-
|
|
139
|
+
const header = await this.readSessionHeader(sessionId);
|
|
140
|
+
const location = persistence.locate(header);
|
|
141
|
+
if (location === void 0 || typeof location.path !== "string") {
|
|
142
|
+
throw new Error(`cannot delete session "${sessionId}": the session persistence backend could not resolve its transcript directory`);
|
|
143
|
+
}
|
|
144
|
+
const transcriptDir = dirname(location.path);
|
|
145
|
+
try {
|
|
146
|
+
await rm(transcriptDir, { recursive: true, force: true });
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const message = `cannot delete session "${sessionId}": transcript directory "${transcriptDir}" remains after bookkeeping cleanup`;
|
|
149
|
+
const detail = `${message}: ${String(error)}`;
|
|
150
|
+
this.ctx.logger.warn(`archive-manager: ${detail}`);
|
|
151
|
+
throw new Error(detail, { cause: error });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
176
154
|
/** Drop the id from every workspace record and refresh entity snapshots. */
|
|
177
155
|
async removeFromWorkspaceAccounts(sessionId) {
|
|
178
156
|
const table = this.requireTable();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-archive-manager",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "NPM-installable DSH Web plugin for managing archived sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.21.0",
|
|
@@ -28,7 +28,21 @@
|
|
|
28
28
|
"test": "node --test test/*.test.mjs",
|
|
29
29
|
"pack:check": "pnpm pack --dry-run",
|
|
30
30
|
"prepublishOnly": "pnpm build && pnpm test"
|
|
31
|
-
},
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
34
|
+
"@deepseek-ai/dsh-api-gateway": "0.1.0-rc.6",
|
|
35
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.0-rc.6",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
|
|
37
|
+
"@deepseek-ai/dsh-client-web-react": "0.1.0-rc.6",
|
|
38
|
+
"@deepseek-ai/dsh-session-projection-cache": "0.1.0-rc.6",
|
|
39
|
+
"@deepseek-ai/dsh-spill-local": "0.1.0-rc.6",
|
|
40
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.6",
|
|
41
|
+
"@deepseek-ai/dsh-typert-registry": "0.1.0-rc.6",
|
|
42
|
+
"@deepseek-ai/dsh-workspace": "0.1.0-rc.6",
|
|
43
|
+
"react": "18.3.1",
|
|
44
|
+
"react-dom": "18.3.1"
|
|
45
|
+
},
|
|
32
46
|
"license": "Apache-2.0",
|
|
33
47
|
"dsh": {
|
|
34
48
|
"bundle": {
|