@michengai/dsh-archive-manager 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -1
- package/CHANGELOG.zh-CN.md +23 -1
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/lib/client.js +46 -19
- package/lib/workspace.js +47 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
[简体中文](CHANGELOG.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Published release notes are retained below; new versions are added without removing earlier entries.
|
|
6
|
+
|
|
7
|
+
## 0.1.16 — 2026-08-27
|
|
8
|
+
|
|
9
|
+
- Added a restore icon to archived-group bulk action menus for consistent action affordances.
|
|
10
|
+
- Shortened the ungrouped bulk action labels to **Restore all** and **Delete all**, avoiding repeated context and reducing menu width.
|
|
11
|
+
|
|
12
|
+
Published package: [`@michengai/dsh-archive-manager@0.1.16`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.16).
|
|
13
|
+
|
|
14
|
+
## 0.1.15 — 2026-08-26
|
|
15
|
+
|
|
16
|
+
- Hardened permanent deletion so missing transcripts clear stale archive, workspace, spill, and projection-cache data while failed physical deletion remains retryable.
|
|
17
|
+
- Cleared workspace and projection-cache tombstones when a session ID is reused, preventing valid replacement sessions from being blocked.
|
|
18
|
+
- Corrected batch deletion feedback and duplicate restore submission handling, and aligned client batch counts with the host's authoritative archive set.
|
|
19
|
+
|
|
20
|
+
Published package: [`@michengai/dsh-archive-manager@0.1.15`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.15).
|
|
6
21
|
|
|
7
22
|
## 0.1.14 — 2026-08-24
|
|
8
23
|
|
|
@@ -40,3 +55,10 @@ Release tag: [`v0.1.11`](https://github.com/MichengAI/dsh-archive-manager/tree/v
|
|
|
40
55
|
- Counted only visible conversations in archive totals.
|
|
41
56
|
|
|
42
57
|
Release tag: [`v0.1.10`](https://github.com/MichengAI/dsh-archive-manager/tree/v0.1.10).
|
|
58
|
+
|
|
59
|
+
## 0.1.9 — 2026-08-17
|
|
60
|
+
|
|
61
|
+
- Removed client-side cascade deletes and surfaced sidebar operation errors.
|
|
62
|
+
- Restyled the project filter and refreshed plugin documentation.
|
|
63
|
+
|
|
64
|
+
Release tag: [`v0.1.9`](https://github.com/MichengAI/dsh-archive-manager/tree/v0.1.9).
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
[English](CHANGELOG.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
以下发布说明会持续保留;新增版本时不再删除较早记录。
|
|
6
|
+
|
|
7
|
+
## 0.1.16 — 2026-08-27
|
|
8
|
+
|
|
9
|
+
- 为归档分组的批量操作菜单补充恢复图标,使操作入口的视觉提示保持一致。
|
|
10
|
+
- 将未分组批量操作文案缩短为「全部恢复」和「全部删除」,避免重复上下文并减小菜单宽度。
|
|
11
|
+
|
|
12
|
+
发布包:[`@michengai/dsh-archive-manager@0.1.16`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.16)。
|
|
13
|
+
|
|
14
|
+
## 0.1.15 — 2026-08-26
|
|
15
|
+
|
|
16
|
+
- 加固永久删除流程:转录已缺失时清理陈旧归档标记、工作区记账、spill 和投影缓存,物理删除失败时仍可重试。
|
|
17
|
+
- 会话 ID 被复用时同步撤销工作区与投影缓存墓碑,避免合法的新会话生命周期被阻断。
|
|
18
|
+
- 修正批量删除反馈和单条恢复重复提交,并使客户端批量计数与宿主权威归档集合保持一致。
|
|
19
|
+
|
|
20
|
+
发布包:[`@michengai/dsh-archive-manager@0.1.15`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.15)。
|
|
6
21
|
|
|
7
22
|
## 0.1.14 — 2026-08-24
|
|
8
23
|
|
|
@@ -40,3 +55,10 @@
|
|
|
40
55
|
- 归档计数仅统计当前可见会话。
|
|
41
56
|
|
|
42
57
|
发布标签:[`v0.1.10`](https://github.com/MichengAI/dsh-archive-manager/tree/v0.1.10)。
|
|
58
|
+
|
|
59
|
+
## 0.1.9 — 2026-08-17
|
|
60
|
+
|
|
61
|
+
- 移除客户端级联删除,并显示侧栏操作错误。
|
|
62
|
+
- 重做项目筛选样式并刷新插件文档。
|
|
63
|
+
|
|
64
|
+
发布标签:[`v0.1.9`](https://github.com/MichengAI/dsh-archive-manager/tree/v0.1.9)。
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**Safely manage archived sessions in DeepSeek Harness**
|
|
10
10
|
|
|
11
|
-
[简体中文](README.zh-CN.md) · [Apache-2.0](LICENSE)
|
|
11
|
+
[简体中文](README.zh-CN.md) · [Changelog](CHANGELOG.md) · [Apache-2.0](LICENSE)
|
|
12
12
|
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
|
|
@@ -105,7 +105,7 @@ dsh plugin --profile web add @michengai/dsh-archive-manager@latest --registry=ht
|
|
|
105
105
|
dsh --profile web --dump-config
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
To pin a release, replace `@latest` with a version such as `@
|
|
108
|
+
To pin a release, replace `@latest` with a specific version such as `@x.y.z`.
|
|
109
109
|
|
|
110
110
|
The configuration output should contain `workspace-archive-manager` and `ui-workspace-archive-manager`. Restart DSH Web and hard-refresh the browser. Do not copy client files manually: the Settings page and archive menu need the mounted plugin.
|
|
111
111
|
|
|
@@ -186,4 +186,4 @@ Licensed under [Apache License 2.0](LICENSE).
|
|
|
186
186
|
|
|
187
187
|
## Changelog
|
|
188
188
|
|
|
189
|
-
See [CHANGELOG.md](CHANGELOG.md) for
|
|
189
|
+
See [CHANGELOG.md](CHANGELOG.md) for retained release notes.
|
package/README.zh-CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**在 DeepSeek Harness 中安全管理已归档会话**
|
|
10
10
|
|
|
11
|
-
[English](README.md) · [Apache-2.0](LICENSE)
|
|
11
|
+
[English](README.md) · [更新日志](CHANGELOG.zh-CN.md) · [Apache-2.0](LICENSE)
|
|
12
12
|
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
|
|
@@ -105,7 +105,7 @@ dsh plugin --profile web add @michengai/dsh-archive-manager@latest --registry=ht
|
|
|
105
105
|
dsh --profile web --dump-config
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
需要钉死某一版时,把 `@latest` 换成具体版本,例如 `@
|
|
108
|
+
需要钉死某一版时,把 `@latest` 换成具体版本,例如 `@x.y.z`。
|
|
109
109
|
|
|
110
110
|
配置输出中应包含 `workspace-archive-manager` 与 `ui-workspace-archive-manager`。安装后重启 DSH Web 并在浏览器硬刷新;请勿手工复制客户端文件,否则设置页和归档菜单不会被挂载。
|
|
111
111
|
|
|
@@ -186,4 +186,4 @@ pnpm pack:check
|
|
|
186
186
|
|
|
187
187
|
## 更新日志
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
完整保留的发布说明见 [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md)。
|
package/lib/client.js
CHANGED
|
@@ -2192,9 +2192,7 @@ window.__ModuleLoader__.load({
|
|
|
2192
2192
|
setDeletingSession(true);
|
|
2193
2193
|
setDeleteSessionError(null);
|
|
2194
2194
|
const rootId = deleteSessionTarget.sessionId;
|
|
2195
|
-
(
|
|
2196
|
-
await deleteSession(rootId);
|
|
2197
|
-
})().then(() => {
|
|
2195
|
+
deleteSession(rootId).then(() => {
|
|
2198
2196
|
setDeletingSession(false);
|
|
2199
2197
|
setDeleteSessionTarget(null);
|
|
2200
2198
|
setDeleteSessionCommittedId(rootId);
|
|
@@ -2728,7 +2726,8 @@ window.__ModuleLoader__.load({
|
|
|
2728
2726
|
const ungrouped = group.key === ARCHIVE_UNGROUPED_KEY;
|
|
2729
2727
|
const items = [{
|
|
2730
2728
|
id: "restore",
|
|
2731
|
-
label: t(ungrouped ? "archives.restoreUngrouped" : "archives.restoreProject")
|
|
2729
|
+
label: t(ungrouped ? "archives.restoreUngrouped" : "archives.restoreProject"),
|
|
2730
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {})
|
|
2732
2731
|
}, {
|
|
2733
2732
|
id: "delete",
|
|
2734
2733
|
label: t(ungrouped ? "archives.deleteUngrouped" : "archives.deleteProject"),
|
|
@@ -2762,7 +2761,7 @@ window.__ModuleLoader__.load({
|
|
|
2762
2761
|
}
|
|
2763
2762
|
/** 客户端只用此派生显示权威批次计数;宿主会用自己的持久状态重新解析。 */
|
|
2764
2763
|
function deriveArchivedBatchIds(archivedSessionIds, items, target) {
|
|
2765
|
-
const ids = [...new Set(
|
|
2764
|
+
const ids = [...new Set(archivedSessionIds ?? [])];
|
|
2766
2765
|
if (target.scope === "all") return ids;
|
|
2767
2766
|
if (target.scope === "workspace") {
|
|
2768
2767
|
const accounted = new Set(items.find((workspace) => workspace.workspaceId === target.workspaceId)?.sessionIds ?? []);
|
|
@@ -2771,6 +2770,24 @@ window.__ModuleLoader__.load({
|
|
|
2771
2770
|
const accounted = new Set(items.flatMap((workspace) => workspace.sessionIds));
|
|
2772
2771
|
return ids.filter((id) => !accounted.has(id));
|
|
2773
2772
|
}
|
|
2773
|
+
/** 把批量删除结果转换为准确区分“已删除”与“陈旧标记已清理”的反馈。 */
|
|
2774
|
+
function archivedDeleteFeedback(result, t) {
|
|
2775
|
+
const deleted = result.deletedSessionIds.length;
|
|
2776
|
+
const skipped = result.skippedSessionIds.length;
|
|
2777
|
+
if (result.failures.length > 0) return {
|
|
2778
|
+
kind: "error",
|
|
2779
|
+
message: t("archives.deletePartial", { deleted, skipped, failed: result.failures.length, detail: result.failures[0].message })
|
|
2780
|
+
};
|
|
2781
|
+
if (skipped > 0 && deleted > 0) return {
|
|
2782
|
+
kind: "notice",
|
|
2783
|
+
message: t("archives.deleteSuccessWithSkipped", { deleted, skipped })
|
|
2784
|
+
};
|
|
2785
|
+
if (skipped > 0) return {
|
|
2786
|
+
kind: "notice",
|
|
2787
|
+
message: t("archives.deleteSkipped", { n: skipped })
|
|
2788
|
+
};
|
|
2789
|
+
return { kind: "notice", message: t("archives.deleteSuccess", { n: deleted }) };
|
|
2790
|
+
}
|
|
2774
2791
|
/**
|
|
2775
2792
|
* 设置页归档分组派生:按工作区分组,组 key 用 workspaceId(上游允许
|
|
2776
2793
|
* 不同路径的工作区同名,title 不能作 React key / 筛选 value),未归入
|
|
@@ -2837,6 +2854,8 @@ window.__ModuleLoader__.load({
|
|
|
2837
2854
|
const [project, setProject] = (0, react.useState)("all");
|
|
2838
2855
|
const [sortBy, setSortBy] = (0, react.useState)("updated");
|
|
2839
2856
|
const [createdAtById, setCreatedAtById] = (0, react.useState)({});
|
|
2857
|
+
const [unarchivingSessionIds, setUnarchivingSessionIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
2858
|
+
const unarchivingSessionIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
2840
2859
|
const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
|
|
2841
2860
|
const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
|
|
2842
2861
|
(0, react.useEffect)(() => {
|
|
@@ -2868,10 +2887,16 @@ window.__ModuleLoader__.load({
|
|
|
2868
2887
|
const allBatchTarget = { scope: "all" };
|
|
2869
2888
|
const allBatchSessionIds = (0, react.useMemo)(() => deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, allBatchTarget), [workspaceState.archivedSessionIds, workspaceState.items]);
|
|
2870
2889
|
const onUnarchive = (sessionId) => {
|
|
2890
|
+
if (busy || unarchivingSessionIdsRef.current.has(sessionId)) return;
|
|
2891
|
+
unarchivingSessionIdsRef.current.add(sessionId);
|
|
2892
|
+
setUnarchivingSessionIds(new Set(unarchivingSessionIdsRef.current));
|
|
2871
2893
|
setError(null);
|
|
2872
2894
|
setNotice(null);
|
|
2873
2895
|
unarchiveSession(sessionId).catch((reason) => {
|
|
2874
2896
|
setError(formatUnarchiveError(reason, t));
|
|
2897
|
+
}).finally(() => {
|
|
2898
|
+
unarchivingSessionIdsRef.current.delete(sessionId);
|
|
2899
|
+
setUnarchivingSessionIds(new Set(unarchivingSessionIdsRef.current));
|
|
2875
2900
|
});
|
|
2876
2901
|
};
|
|
2877
2902
|
const onBatchUnarchive = async (target) => {
|
|
@@ -2911,12 +2936,9 @@ window.__ModuleLoader__.load({
|
|
|
2911
2936
|
try {
|
|
2912
2937
|
if (deleteTarget.kind === "batch") {
|
|
2913
2938
|
const result = await deleteArchivedSessions(deleteTarget.target);
|
|
2914
|
-
const
|
|
2915
|
-
if (
|
|
2916
|
-
|
|
2917
|
-
} else {
|
|
2918
|
-
setNotice(t("archives.deleteSuccess", { n: completed }));
|
|
2919
|
-
}
|
|
2939
|
+
const feedback = archivedDeleteFeedback(result, t);
|
|
2940
|
+
if (feedback.kind === "error") setError(feedback.message);
|
|
2941
|
+
else setNotice(feedback.message);
|
|
2920
2942
|
} else {
|
|
2921
2943
|
await deleteSession(deleteTarget.session.id);
|
|
2922
2944
|
}
|
|
@@ -2945,7 +2967,7 @@ window.__ModuleLoader__.load({
|
|
|
2945
2967
|
className: "dsham_settingsRow",
|
|
2946
2968
|
children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsContent", children: [(0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsTitle", children: displayTitle(session, t) }), (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsMeta", children: archiveTimeLabel(session.updatedAt, t) })] }), (0, react_jsx_runtime.jsxs)("div", {
|
|
2947
2969
|
className: "dsham_settingsActions",
|
|
2948
|
-
children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy, onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy, "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
|
|
2970
|
+
children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
|
|
2949
2971
|
})]
|
|
2950
2972
|
}, session.id))
|
|
2951
2973
|
})]
|
|
@@ -3014,9 +3036,9 @@ window.__ModuleLoader__.load({
|
|
|
3014
3036
|
"archives.timestamp": "{date},{time}",
|
|
3015
3037
|
"archives.restoreAll": "全部恢复",
|
|
3016
3038
|
"archives.restoreProject": "恢复该项目的全部聊天",
|
|
3017
|
-
"archives.restoreUngrouped": "
|
|
3039
|
+
"archives.restoreUngrouped": "全部恢复",
|
|
3018
3040
|
"archives.deleteProject": "删除该项目的全部聊天",
|
|
3019
|
-
"archives.deleteUngrouped": "
|
|
3041
|
+
"archives.deleteUngrouped": "全部删除",
|
|
3020
3042
|
"archives.projectActions": "项目“{name}”的归档操作",
|
|
3021
3043
|
"archives.ungroupedActions": "未分组聊天的归档操作",
|
|
3022
3044
|
"archives.restoreSuccess": "已恢复 {n} 个已归档聊天。",
|
|
@@ -3033,7 +3055,9 @@ window.__ModuleLoader__.load({
|
|
|
3033
3055
|
"archives.deleteUngroupedConfirm": "删除未分组的全部聊天",
|
|
3034
3056
|
"archives.deleteBatchPending": "正在删除已归档聊天…",
|
|
3035
3057
|
"archives.deleteSuccess": "已删除 {n} 个已归档聊天。",
|
|
3036
|
-
"archives.
|
|
3058
|
+
"archives.deleteSuccessWithSkipped": "已删除 {deleted} 个已归档聊天;另清理 {skipped} 个已不存在会话的归档记录。",
|
|
3059
|
+
"archives.deleteSkipped": "已清理 {n} 个已不存在会话的归档记录。",
|
|
3060
|
+
"archives.deletePartial": "已删除 {deleted} 个聊天,清理 {skipped} 个已不存在会话的归档记录;{failed} 个删除失败:{detail}",
|
|
3037
3061
|
"archives.unarchiveUnknown": "会话已不存在,无法取消归档。",
|
|
3038
3062
|
"archives.unarchiveFailed": "取消归档失败:{detail}",
|
|
3039
3063
|
"archives.archiveUnknown": "会话已不存在,无法归档。",
|
|
@@ -3128,9 +3152,9 @@ window.__ModuleLoader__.load({
|
|
|
3128
3152
|
"archives.timestamp": "{date}, {time}",
|
|
3129
3153
|
"archives.restoreAll": "Restore all",
|
|
3130
3154
|
"archives.restoreProject": "Restore all chats in this project",
|
|
3131
|
-
"archives.restoreUngrouped": "Restore all
|
|
3155
|
+
"archives.restoreUngrouped": "Restore all",
|
|
3132
3156
|
"archives.deleteProject": "Delete all chats in this project",
|
|
3133
|
-
"archives.deleteUngrouped": "Delete all
|
|
3157
|
+
"archives.deleteUngrouped": "Delete all",
|
|
3134
3158
|
"archives.projectActions": "Archive actions for project {name}",
|
|
3135
3159
|
"archives.ungroupedActions": "Archive actions for ungrouped chats",
|
|
3136
3160
|
"archives.restoreSuccess": "Restored {n} archived chats.",
|
|
@@ -3147,7 +3171,9 @@ window.__ModuleLoader__.load({
|
|
|
3147
3171
|
"archives.deleteUngroupedConfirm": "Delete all ungrouped chats",
|
|
3148
3172
|
"archives.deleteBatchPending": "Deleting archived chats…",
|
|
3149
3173
|
"archives.deleteSuccess": "Deleted {n} archived chats.",
|
|
3150
|
-
"archives.
|
|
3174
|
+
"archives.deleteSuccessWithSkipped": "Deleted {deleted} archived chats and cleared {skipped} stale archive entries for sessions that no longer exist.",
|
|
3175
|
+
"archives.deleteSkipped": "Cleared {n} stale archive entries for sessions that no longer exist.",
|
|
3176
|
+
"archives.deletePartial": "Deleted {deleted} chats and cleared {skipped} stale archive entries; {failed} could not be deleted: {detail}",
|
|
3151
3177
|
"archives.unarchiveUnknown": "This session no longer exists, so it cannot be unarchived.",
|
|
3152
3178
|
"archives.unarchiveFailed": "Could not unarchive the session: {detail}",
|
|
3153
3179
|
"archives.archiveUnknown": "This session no longer exists, so it cannot be archived.",
|
|
@@ -3374,7 +3400,7 @@ window.__ModuleLoader__.load({
|
|
|
3374
3400
|
}
|
|
3375
3401
|
//#endregion
|
|
3376
3402
|
/** Pure derivation surface for dsh-archive-manager self-tests (no-op for the runtime). */
|
|
3377
|
-
exports.__test = {
|
|
3403
|
+
exports.__test = {
|
|
3378
3404
|
displayTitle,
|
|
3379
3405
|
sessionVisible,
|
|
3380
3406
|
isUnknownSessionError,
|
|
@@ -3384,6 +3410,7 @@ window.__ModuleLoader__.load({
|
|
|
3384
3410
|
deriveArchivedGroups,
|
|
3385
3411
|
sortArchivedGroups,
|
|
3386
3412
|
deriveArchivedBatchIds,
|
|
3413
|
+
archivedDeleteFeedback,
|
|
3387
3414
|
createWorkspaceViewStore,
|
|
3388
3415
|
groupByWorkspace,
|
|
3389
3416
|
byRecency,
|
package/lib/workspace.js
CHANGED
|
@@ -15,8 +15,9 @@ import { trackTombstone } from "./tombstone.js";
|
|
|
15
15
|
* 删除投影缓存,级联删除 SUBAGENT 子会话并清理 spill;最后才删除转录
|
|
16
16
|
* 目录。fork 分支虽有 `parentSession`,但属于独立用户会话,不参与级联。
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* 父会话的可失败记账清理先于其转录目录删除;父目录删除失败时保留头部
|
|
19
|
+
* 索引,允许再次调用同一入口完成删除。SUBAGENT 级联跨多个转录目录,无法
|
|
20
|
+
* 组成事务:子会话可能已先删除,重试会跳过它们并继续处理仍保留的父会话。
|
|
20
21
|
* 物理删除成功后必须遗忘父类内存索引(headers / sessionPaths /
|
|
21
22
|
* invalidSessionPaths)并打上删除墓碑:父类 sessionKnown 以 headers.has
|
|
22
23
|
* 短路,indexHeaders 只增不减,stale list() 否则会把已删 id 救活,
|
|
@@ -322,7 +323,14 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
322
323
|
deletedSessionIds.push(sessionId);
|
|
323
324
|
} catch (error) {
|
|
324
325
|
if (error instanceof ArchiveUnknownSessionError) {
|
|
325
|
-
|
|
326
|
+
// 转录已消失时仍需清完可达的持久痕迹;只有全部完成才算
|
|
327
|
+
// skipped,否则保留归档标记并作为 failure 暴露,允许重试。
|
|
328
|
+
try {
|
|
329
|
+
await this.cleanupUnknownArchivedSession(sessionId);
|
|
330
|
+
skippedSessionIds.push(sessionId);
|
|
331
|
+
} catch (cleanupError) {
|
|
332
|
+
failures.push({ sessionId, message: String(cleanupError) });
|
|
333
|
+
}
|
|
326
334
|
continue;
|
|
327
335
|
}
|
|
328
336
|
failures.push({ sessionId, message: String(error) });
|
|
@@ -331,6 +339,31 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
331
339
|
return { requestedSessionIds, deletedSessionIds, skippedSessionIds, failures };
|
|
332
340
|
});
|
|
333
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* 清理已无转录的陈旧归档项。缓存墓碑只在清除在途写入期间短暂持有:
|
|
344
|
+
* workspace 没有可记录的旧 header 身份,永久保留它会挡住未来的冷复用。
|
|
345
|
+
* 归档标记最后清除,前序可失败步骤出错时批量入口仍能再次命中。
|
|
346
|
+
*/
|
|
347
|
+
async cleanupUnknownArchivedSession(sessionId) {
|
|
348
|
+
const projCache = this.ctx.get("sessionProjectionCache");
|
|
349
|
+
await projCache?.whenIdle?.();
|
|
350
|
+
if (projCache !== void 0) {
|
|
351
|
+
await projCache.delete(sessionId);
|
|
352
|
+
// 等待删除期间可能已进入的写回观察到墓碑并完成补删,再允许
|
|
353
|
+
// 同 id 的未来新生命周期写入缓存。
|
|
354
|
+
await projCache.whenIdle?.();
|
|
355
|
+
projCache.clearTombstone?.(sessionId);
|
|
356
|
+
}
|
|
357
|
+
await this.cleanSpill(sessionId);
|
|
358
|
+
await this.removeFromWorkspaceAccounts(sessionId);
|
|
359
|
+
const state = this.requireState();
|
|
360
|
+
if (state.archivedSessionIds.includes(sessionId)) {
|
|
361
|
+
await this.setState({
|
|
362
|
+
...state,
|
|
363
|
+
archivedSessionIds: state.archivedSessionIds.filter((id) => id !== sessionId)
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
334
367
|
/** 以归档集合顺序解析批量目标,避免依赖浏览器尚未加载完整的摘要投影。 */
|
|
335
368
|
archivedSessionIdsForTarget(target) {
|
|
336
369
|
target = archivedBatchTargetSchema.parse(target);
|
|
@@ -381,7 +414,8 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
381
414
|
// dispose 的写后落盘必须先于缓存行删除完成,
|
|
382
415
|
// 否则该行会在删除之后被写回(复活)。
|
|
383
416
|
await projCache?.whenIdle?.();
|
|
384
|
-
//
|
|
417
|
+
// 先完成父会话的可失败记账清理;SUBAGENT 级联跨目录且非事务,可能
|
|
418
|
+
// 部分完成,但父转录失败时仍保留索引供重试。
|
|
385
419
|
const state = this.requireState();
|
|
386
420
|
if (state.archivedSessionIds.includes(sessionId)) {
|
|
387
421
|
await this.setState({
|
|
@@ -403,12 +437,15 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
403
437
|
* 从父类内存索引中遗忘已删除会话,并阻止后续 indexHeaders 把它加回。
|
|
404
438
|
* 实时会话以同 id 重新出现时(自定义 id 复用)会撤掉墓碑。
|
|
405
439
|
*/
|
|
406
|
-
clearTombstone(sessionId) {
|
|
407
|
-
this.deletedSessionIds.delete(sessionId);
|
|
408
|
-
this.deletedIdentities.delete(sessionId);
|
|
409
|
-
const idx = this.deletedSessionOrder.indexOf(sessionId);
|
|
410
|
-
if (idx !== -1) this.deletedSessionOrder.splice(idx, 1);
|
|
411
|
-
|
|
440
|
+
clearTombstone(sessionId) {
|
|
441
|
+
this.deletedSessionIds.delete(sessionId);
|
|
442
|
+
this.deletedIdentities.delete(sessionId);
|
|
443
|
+
const idx = this.deletedSessionOrder.indexOf(sessionId);
|
|
444
|
+
if (idx !== -1) this.deletedSessionOrder.splice(idx, 1);
|
|
445
|
+
// workspace 与 projection-cache 共同描述同一删除生命周期;新生命周期
|
|
446
|
+
// 被接纳时必须同步撤销两处墓碑,否则缓存写入仍会永久被拦截。
|
|
447
|
+
this.ctx.get("sessionProjectionCache")?.clearTombstone?.(sessionId);
|
|
448
|
+
}
|
|
412
449
|
forgetIndexedSession(sessionId) {
|
|
413
450
|
for (const evicted of trackTombstone(this.deletedSessionIds, this.deletedSessionOrder, sessionId, this.deletedSessionTombstoneLimit)) this.deletedIdentities.delete(evicted);
|
|
414
451
|
this.headers.delete(sessionId);
|